Completed
Push — 1.0 ( c1044b...753b1d )
by David
02:30
created

RunCommand::getCloverFileFromBranch()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 4
1
<?php
2
namespace TheCodingMachine\WashingMachine\Commands;
3
4
use Gitlab\Client;
5
use Gitlab\Exception\RuntimeException;
6
use Symfony\Component\Console\Command\Command;
7
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Input\InputOption;
9
use Symfony\Component\Console\Output\OutputInterface;
10
use TheCodingMachine\WashingMachine\Clover\CloverFile;
11
use TheCodingMachine\WashingMachine\Clover\Crap4JFile;
12
use TheCodingMachine\WashingMachine\Clover\CrapMethodFetcherInterface;
13
use TheCodingMachine\WashingMachine\Clover\CrapMethodMerger;
14
use TheCodingMachine\WashingMachine\Clover\DiffService;
15
use TheCodingMachine\WashingMachine\Clover\EmptyCloverFile;
16
use TheCodingMachine\WashingMachine\Gitlab\BuildNotFoundException;
17
use TheCodingMachine\WashingMachine\Gitlab\BuildService;
18
use TheCodingMachine\WashingMachine\Gitlab\MergeRequestNotFoundException;
19
use TheCodingMachine\WashingMachine\Gitlab\Message;
20
use TheCodingMachine\WashingMachine\Gitlab\SendCommentService;
21
22
class RunCommand extends Command
23
{
24
25
    protected function configure()
26
    {
27
        $this
28
            ->setName('run')
29
            ->setDescription('Analyses the coverage report files and upload the result to Gitlab')
30
            //->setHelp("This command allows you to create users...")
31
            ->addOption('clover',
32
                'c',
33
                InputOption::VALUE_REQUIRED,
34
                'The path to the clover.xml file generated by PHPUnit.',
35
                'clover.xml')
36
            ->addOption('crap4j',
37
                'j',
38
                InputOption::VALUE_REQUIRED,
39
                'The path to the crap4j.xml file generated by PHPUnit.',
40
                'crap4j.xml')
41
            ->addOption('gitlab-url',
42
                'u',
43
                InputOption::VALUE_REQUIRED,
44
                'The Gitlab URL. If not specified, it is deduced from the CI_BUILD_REPO environment variable.',
45
                null)
46
            ->addOption('gitlab-api-token',
47
                't',
48
                InputOption::VALUE_REQUIRED,
49
                'The Gitlab API token. If not specified, it is fetched from the GITLAB_API_TOKEN environment variable.',
50
                null)
51
            /*->addOption('gitlab-project-id',
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
52
                'p',
53
                InputOption::VALUE_REQUIRED,
54
                'The Gitlab project ID. If not specified, it is fetched from the CI_PROJECT_ID environment variable.',
55
                null)*/
56
            ->addOption('gitlab-project-name',
57
                'p',
58
                InputOption::VALUE_REQUIRED,
59
                'The Gitlab project name (in the form "group/name"). If not specified, it is deduced from the CI_PROJECT_DIR environment variable.',
60
                null)
61
            ->addOption('gitlab-build-ref',
62
                'r',
63
                InputOption::VALUE_REQUIRED,
64
                'The Gitlab CI build reference. If not specified, it is deduced from the CI_BUILD_REF environment variable.',
65
                null)
66
        ;
67
    }
68
69
    protected function execute(InputInterface $input, OutputInterface $output)
70
    {
71
        $config = new Config($input);
72
73
        $cloverFilePath = $config->getCloverFilePath();
74
75
        $cloverFile = null;
76
77
        if (file_exists($cloverFilePath)) {
78
            $cloverFile = CloverFile::fromFile($cloverFilePath, getcwd());
79
            //$output->writeln(sprintf('Code coverage: %.2f%%', $cloverFile->getCoveragePercentage() * 100));
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
80
        }
81
82
        $crap4JFilePath = $config->getCrap4JFilePath();
83
84
        if (file_exists($crap4JFilePath)) {
85
            $crap4jFile = Crap4JFile::fromFile($crap4JFilePath);
86
        }
87
88
        $methodsProvider = null;
0 ignored issues
show
Unused Code introduced by
$methodsProvider is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
89
        $codeCoverageProvider = null;
90
91 View Code Duplication
        if ($cloverFile !== null && $crap4jFile !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
92
            $methodsProvider = new CrapMethodMerger($cloverFile, $crap4jFile);
0 ignored issues
show
Bug introduced by
The variable $crap4jFile does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
93
            $codeCoverageProvider = $cloverFile;
94
        } elseif ($cloverFile !== null) {
95
            $methodsProvider = $cloverFile;
96
            $codeCoverageProvider = $cloverFile;
97
        } elseif ($crap4jFile !== null) {
98
            $methodsProvider = $crap4jFile;
99
        } else {
100
            throw new \RuntimeException('Could not find nor clover file, neither crap4j file for analysis. Searched paths: '.$cloverFilePath.' and '.$crap4JFilePath);
101
        }
102
103
        $gitlabApiToken = $config->getGitlabApiToken();
104
105
        $gitlabUrl = $config->getGitlabUrl();
106
        $gitlabApiUrl = $config->getGitlabApiUrl();
107
108
109
        /*$projectId = $input->getOption('gitlab-project-id');
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
110
        if ($projectId === null) {
111
            $projectId = getenv('CI_PROJECT_ID');
112
            if ($projectId === false) {
113
                throw new \RuntimeException('Could not find the Gitlab project ID in the "CI_PROJECT_ID" environment variable (usually set by Gitlab CI). Either set this environment variable or pass the ID via the --gitlab-project-id command line option.');
114
            }
115
        }*/
116
117
        $projectName = $config->getGitlabProjectName();
118
119
        $buildRef = $config->getGitlabBuildRef();
120
121
        $currentBranchName = $config->getCurrentBranchName();
122
123
        $client = new Client($gitlabApiUrl);
124
        $client->authenticate($gitlabApiToken);
125
126
        $diffService = new DiffService(1, 20);
127
128
        $sendCommentService = new SendCommentService($client, $diffService);
129
130
        // From CI_BUILD_REF, we can get the commit ( -> project -> build -> commit )
131
        // From the merge_requests API, we can get the list of commits for a single merge request
132
        // Hence, we can find the merge_request matching a build!
133
134
        $buildService = new BuildService($client);
135
136
        try {
137
            $mergeRequest = $buildService->findMergeRequestByBuildRef($projectName, $buildRef);
138
139
140
            try {
141
                list($previousCodeCoverageProvider, $previousMethodsProvider) = $this->getMeasuresFromBranch($buildService, $mergeRequest['target_project_id'], $mergeRequest['target_branch'], $cloverFilePath, $crap4JFilePath);
142
            } catch (RuntimeException $e) {
143
                if ($e->getCode() === 404) {
144
                    // We could not find a previous clover file in the master branch.
145
                    // Maybe this branch is the first to contain clover files?
146
                    // Let's deal with this by generating a fake "empty" clover file.
147
                    $previousCodeCoverageProvider = EmptyCloverFile::create();
148
                    $previousMethodsProvider = EmptyCloverFile::create();
149
                } else {
150
                    throw $e;
151
                }
152
            }
153
154
            $message = new Message();
155
            $message->addCoverageMessage($codeCoverageProvider, $previousCodeCoverageProvider);
0 ignored issues
show
Bug introduced by
It seems like $codeCoverageProvider defined by null on line 89 can be null; however, TheCodingMachine\Washing...e::addCoverageMessage() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
156
            $message->addDifferencesHtml($methodsProvider, $previousMethodsProvider, $diffService);
0 ignored issues
show
Bug introduced by
The call to addDifferencesHtml() misses some required arguments starting with $commitId.
Loading history...
157
158
            $client->merge_requests->addComment($projectName, $mergeRequest['id'], (string) $message);
159
160
        } catch (MergeRequestNotFoundException $e) {
161
            // If there is no merge request attached to this build, let's skip the merge request comment. We can still make some comments on the commit itself!
162
163
            $output->writeln('It seems that this CI build is not part of a merge request. Skipping.');
164
        }
165
166
        try {
167
            $targetProjectId = $mergeRequest['target_project_id'] ?? $projectName;
168
            list($lastCommitCloverFile) = $this->getMeasuresFromBranch($buildService, $targetProjectId, $currentBranchName, $cloverFilePath, $crap4JFilePath);
169
170
            $sendCommentService->sendDifferencesCommentsInCommit($cloverFile, $lastCommitCloverFile, $projectName, $buildRef, $gitlabUrl);
0 ignored issues
show
Bug introduced by
It seems like $cloverFile defined by null on line 75 can be null; however, TheCodingMachine\Washing...encesCommentsInCommit() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
171
        } catch (BuildNotFoundException $e) {
172
            $output->writeln('Unable to find a previous build for this branch. Skipping adding comments inside the commit. '.$e->getMessage());
173
        }
174
175
    }
176
177
    /**
178
     * @param BuildService $buildService
179
     * @param string $projectName
180
     * @param string $targetBranch
181
     * @param string $cloverPath
182
     * @param string $crap4JPath
183
     * @return array First element: code coverage, second element: list of methods.
184
     */
185
    public function getMeasuresFromBranch(BuildService $buildService, string $projectName, string $targetBranch, string $cloverPath, string $crap4JPath) : array
186
    {
187
        $tmpFile = tempnam(sys_get_temp_dir(), 'art').'.zip';
188
189
        $buildService->dumpArtifactFromBranch($projectName, $targetBranch, $tmpFile);
190
        $zipFile = new \ZipArchive();
191
        if ($zipFile->open($tmpFile)!==true) {
192
            throw new \RuntimeException('Invalid ZIP archive '.$tmpFile);
193
        }
194
        $cloverFileString = $zipFile->getFromName($cloverPath);
195
196
        $cloverFile = null;
197
        if ($cloverFileString !== false) {
198
            $cloverFile = CloverFile::fromString($cloverFileString, getcwd());
199
        }
200
201
        $crap4JString = $zipFile->getFromName($crap4JPath);
202
203
        $crap4JFile = null;
204
        if ($crap4JString !== false) {
205
            $crap4JFile = Crap4JFile::fromString($crap4JString);
206
        }
207
208
        $methodsProvider = null;
0 ignored issues
show
Unused Code introduced by
$methodsProvider is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
209
        $codeCoverageProvider = null;
210
211 View Code Duplication
        if ($cloverFile !== null && $crap4JFile !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
212
            $methodsProvider = new CrapMethodMerger($cloverFile, $crap4JFile);
213
            $codeCoverageProvider = $cloverFile;
214
        } elseif ($cloverFile !== null) {
215
            $methodsProvider = $cloverFile;
216
            $codeCoverageProvider = $cloverFile;
217
        } elseif ($crap4JFile !== null) {
218
            $methodsProvider = $crap4JFile;
219
        } else {
220
            throw new \RuntimeException('Could not find nor clover file, neither crap4j file for analysis. Searched paths: '.$cloverFilePath.' and '.$crap4JFilePath);
221
        }
222
223
        return [$codeCoverageProvider, $methodsProvider];
224
    }
225
}
226
227
/*
228
=================ENV IN A PR CONTEXT =========================
229
230
CI_BUILD_TOKEN=xxxxxx
231
HOSTNAME=runner-9431b96d-project-428-concurrent-0
232
PHP_INI_DIR=/usr/local/etc/php
233
PHP_ASC_URL=https://secure.php.net/get/php-7.0.15.tar.xz.asc/from/this/mirror
234
CI_BUILD_BEFORE_SHA=7af13f8e3bd090c7c34750e4badfc66a5f0af110
235
CI_SERVER_VERSION=
236
CI_BUILD_ID=109
237
OLDPWD=/
238
PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2
239
PHP_MD5=dca23412f3e3b3987e582091b751925d
240
CI_PROJECT_ID=428
241
PHPIZE_DEPS=autoconf 		file 		g++ 		gcc 		libc-dev 		make 		pkg-config 		re2c
242
PHP_URL=https://secure.php.net/get/php-7.0.15.tar.xz/from/this/mirror
243
CI_BUILD_REF_NAME=feature/js-ci
244
CI_BUILD_REF=7af13f8e3bd090c7c34750e4badfc66a5f0af110
245
PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie
246
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
247
CI_BUILD_STAGE=test
248
CI_PROJECT_DIR=/builds/tcm-projects/uneo
249
PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2
250
GPG_KEYS=1A4E8B7277C42E53DBA9C7B9BCAA30EA9C0D5763 6E4F6AB321FDC07F2C332E3AC2BF0BC433CFC8B3
251
PWD=/builds/tcm-projects/uneo
252
CI_DEBUG_TRACE=false
253
CI_SERVER_NAME=GitLab CI
254
XDEBUG_VERSION=2.5.0
255
GITLAB_CI=true
256
CI_SERVER_REVISION=
257
CI_BUILD_NAME=test:app
258
HOME=/root
259
SHLVL=1
260
PHP_SHA256=300364d57fc4a6176ff7d52d390ee870ab6e30df121026649f8e7e0b9657fe93
261
CI_SERVER=yes
262
CI=true
263
CI_BUILD_REPO=http://gitlab-ci-token:[email protected]/tcm-projects/uneo.git
264
PHP_VERSION=7.0.15
265
266
===================ENV IN A COMMIT CONTEXT
267
268
CI_BUILD_TOKEN=xxxxxx
269
HOSTNAME=runner-9431b96d-project-447-concurrent-0
270
PHP_INI_DIR=/usr/local/etc/php
271
PHP_ASC_URL=https://secure.php.net/get/php-7.0.15.tar.xz.asc/from/this/mirror
272
CI_BUILD_BEFORE_SHA=42dd9686eafc2e8fb0a6b4d2c6785baec229c94a
273
CI_SERVER_VERSION=
274
CI_BUILD_ID=192
275
OLDPWD=/
276
PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2
277
PHP_MD5=dca23412f3e3b3987e582091b751925d
278
CI_PROJECT_ID=447
279
GITLAB_API_TOKEN=xxxxxxxxxxxxxxchangedmanually
280
PHPIZE_DEPS=autoconf 		file 		g++ 		gcc 		libc-dev 		make 		pkg-config 		re2c
281
PHP_URL=https://secure.php.net/get/php-7.0.15.tar.xz/from/this/mirror
282
CI_BUILD_REF_NAME=master
283
CI_BUILD_REF=42dd9686eafc2e8fb0a6b4d2c6785baec229c94a
284
PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie
285
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
286
CI_BUILD_STAGE=test
287
CI_PROJECT_DIR=/builds/dan/washing-test
288
PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2
289
GPG_KEYS=1A4E8B7277C42E53DBA9C7B9BCAA30EA9C0D5763 6E4F6AB321FDC07F2C332E3AC2BF0BC433CFC8B3
290
PWD=/builds/dan/washing-test
291
CI_DEBUG_TRACE=false
292
CI_SERVER_NAME=GitLab CI
293
XDEBUG_VERSION=2.5.0
294
GITLAB_CI=true
295
CI_SERVER_REVISION=
296
CI_BUILD_NAME=test
297
HOME=/root
298
SHLVL=1
299
PHP_SHA256=300364d57fc4a6176ff7d52d390ee870ab6e30df121026649f8e7e0b9657fe93
300
CI_SERVER=yes
301
CI=true
302
CI_BUILD_REPO=http://gitlab-ci-token:[email protected]/dan/washing-test.git
303
PHP_VERSION=7.0.15
304
*/