Passed
Push — master ( 972120...1c77fc )
by Michiel
08:19
created

ApplyTaskTest::testOutput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 0
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing\Tasks\System;
21
22
use Exception;
23
use Phing\Io\FileUtils;
24
use Phing\Io\File;
25
use Phing\Support\BuildFileTest;
26
use Phing\Target;
27
use Phing\Task;
28
use Phing\Tasks\System\ApplyTask;
29
use Phing\UnknownElement;
30
use ReflectionProperty;
31
32
/**
33
 * Tests the Apply Task
34
 *
35
 * @author  Utsav Handa <handautsav at hotmail dot com>
36
 * @package phing.tasks.system
37
 */
38
class ApplyTaskTest extends BuildFileTest
39
{
40
    /**
41
     * Whether test is being run on windows
42
     * @var bool
43
     */
44
    protected $windows;
45
46
    /**
47
     * Setup the test
48
     */
49
    public function setUp(): void
50
    {
51
        // Tests definitions
52
        $this->configureProject(PHING_TEST_BASE . '/etc/tasks/system/ApplyTest.xml');
53
54
        // Identifying the running environment
55
        $this->windows = strtoupper(substr(PHP_OS, 0, 3)) == 'WIN';
56
    }
57
58
    /**********************************************************************************/
59
    /************************************** T E S T S *********************************/
60
    /**********************************************************************************/
61
62
    /**
63
     * Tests the OS configuration setting
64
     */
65
    public function testPropertySetOs()
66
    {
67
        $this->assertAttributeIsSetTo('os', 'linux');
68
    }
69
70
    /**
71
     * Tests the dir configuration setting
72
     */
73
    public function testPropertySetDir()
74
    {
75
        $this->assertAttributeIsSetTo('dir', new File($this->project->getProperty('php.tmpdir')));
76
    }
77
78
    /**
79
     * Tests the escape configuration setting
80
     */
81
    public function testPropertySetEscape()
82
    {
83
        $this->assertAttributeIsSetTo('escape', true);
84
    }
85
86
    /**
87
     * Tests the pass-thru configuration setting
88
     */
89
    public function testPropertySetPassthru()
90
    {
91
        $this->assertAttributeIsSetTo('passthru', true);
92
    }
93
94
    /**
95
     * Tests the spawn configuration setting
96
     */
97
    public function testPropertySetSpawn()
98
    {
99
        $this->assertAttributeIsSetTo('spawn', true);
100
    }
101
102
    /**
103
     * Tests the returnProperty configuration setting
104
     */
105
    public function testPropertySetReturnProperty()
106
    {
107
        $this->assertAttributeIsSetTo('returnProperty', 'retval');
108
    }
109
110
    /**
111
     * Tests the outputProperty configuration setting
112
     */
113
    public function testPropertySetOutputProperty()
114
    {
115
        $this->assertAttributeIsSetTo('outputProperty', 'outval');
116
    }
117
118
    /**
119
     * Tests the checkReturn/failonerror configuration setting
120
     */
121
    public function testPropertySetCheckReturn()
122
    {
123
        $this->assertAttributeIsSetTo('checkreturn', true);
124
    }
125
126
    /**
127
     * Tests the output configuration setting
128
     */
129
    public function testPropertySetOutput()
130
    {
131
        $this->assertAttributeIsSetTo(
132
            'output',
133
            new File($this->project->getProperty('php.tmpdir') . '/outputfilename')
134
        );
135
    }
136
137
    /**
138
     * Tests the error configuration setting
139
     */
140
    public function testPropertySetError()
141
    {
142
        $this->assertAttributeIsSetTo(
143
            'error',
144
            new File($this->project->getProperty('php.tmpdir') . '/errorfilename')
145
        );
146
    }
147
148
    /**
149
     * Tests the append configuration setting
150
     */
151
    public function testPropertySetAppend()
152
    {
153
        $this->assertAttributeIsSetTo('append', true, 'appendoutput');
154
    }
155
156
    /**
157
     * Tests the parallel configuration setting
158
     */
159
    public function testPropertySetParallel()
160
    {
161
        $this->assertAttributeIsSetTo('parallel', false);
162
    }
163
164
    /**
165
     * Tests the addsourcefile configuration setting
166
     */
167
    public function testPropertySetAddsourcefile()
168
    {
169
        $this->assertAttributeIsSetTo('addsourcefile', false);
170
    }
171
172
    /**
173
     * Tests the relative configuration setting
174
     */
175
    public function testPropertySetRelative()
176
    {
177
        $this->assertAttributeIsSetTo('relative', false);
178
    }
179
180
    /**
181
     * Tests the forwardslash configuration setting
182
     */
183
    public function testPropertySetForwardslash()
184
    {
185
        $this->assertAttributeIsSetTo('forwardslash', true);
186
    }
187
188
    /**
189
     * Tests the maxparallel configuration setting
190
     */
191
    public function testPropertySetMaxparallel()
192
    {
193
        $this->assertAttributeIsSetTo('maxparallel', 10);
194
    }
195
196
    /**
197
     * Tests the OS execution for the unspecified OS
198
     */
199
    public function testDoNotExecuteOnWrongOs()
200
    {
201
202
        // Process
203
        $this->executeTarget(__FUNCTION__);
204
        $this->assertInLogs('was not found in the specified list of valid OSes: unknownos');
205
206
        $this->assertStringNotContainsString('this should not be executed', $this->getOutput());
207
    }
208
209
    /**
210
     * Tests the OS execution for the specified OS list
211
     */
212
    public function testExecuteOnCorrectOs()
213
    {
214
        $this->executeTarget(__FUNCTION__);
215
        $this->assertInLogs('this should be executed');
216
    }
217
218
    /**
219
     * Tests the dir changing on a non-existent directory
220
     */
221
    public function testFailOnNonExistingDir()
222
    {
223
        $nonExistentDir = $this->project->getProperty('php.tmpdir') . DIRECTORY_SEPARATOR
224
            . 'non' . DIRECTORY_SEPARATOR
225
            . 'existent' . DIRECTORY_SEPARATOR
226
            . 'dir';
227
228
        return $this->expectBuildExceptionContaining(
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->expectBuildExcept...not a valid directory') targeting Phing\Support\BuildFileT...ldExceptionContaining() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
229
            __FUNCTION__,
230
            __FUNCTION__,
231
            "'$nonExistentDir' is not a valid directory"
232
        );
233
    }
234
235
    /**
236
     * Tests the dir changing on an existent directory
237
     *
238
     * @requires OS ^(?:(?!Win).)*$
239
     */
240
    public function testChangeToDir()
241
    {
242
        $this->executeTarget(__FUNCTION__);
243
        $this->assertInLogs('Working directory change successful');
244
    }
245
246
    /**
247
     * Tests the failonerror/checkreturn value for 'true'
248
     *
249
     * @requires OS ^(?:(?!Win).)*$
250
     */
251
    public function testCheckreturnTrue()
252
    {
253
        $this->executeTarget(__FUNCTION__);
254
        $this->assertTrue(true);
255
    }
256
257
    /**
258
     * Tests the failonerror/checkreturn value for 'false'
259
     *
260
     * @requires OS ^(?:(?!Win).)*$
261
     */
262
    public function testCheckreturnFalse()
263
    {
264
        return $this->expectBuildExceptionContaining(__FUNCTION__, __FUNCTION__, 'Task exited with code (1)');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->expectBuildExcept... exited with code (1)') targeting Phing\Support\BuildFileT...ldExceptionContaining() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
265
    }
266
267
    /**
268
     * Tests the outputProperty setting
269
     */
270
    public function testOutputProperty()
271
    {
272
        $this->executeTarget(__FUNCTION__);
273
        $this->assertInLogs('The output property\'s value is: "foo"');
274
    }
275
276
    /**
277
     * Tests the returnProperty setting
278
     */
279
    public function testReturnProperty()
280
    {
281
        $this->executeTarget(__FUNCTION__);
282
        $this->assertInLogs('The return property\'s value is: "1"');
283
    }
284
285
    /**
286
     * Tests the command escaping for execution
287
     */
288
    public function testEscape()
289
    {
290
        $this->executeTarget(__FUNCTION__);
291
        $this->assertInLogs(
292
            $this->windows
293
                ? (escapeshellarg('echo') . ' ' . escapeshellarg('foo') . " " . escapeshellarg('|') . " " . escapeshellarg('cat'))
294
                : ("'echo' 'foo' '|' 'cat'")
295
        );
296
    }
297
298
    /**
299
     * Tests the command execution with 'passthru' function
300
     */
301
    public function testPassThru()
302
    {
303
        $this->executeTarget(__FUNCTION__);
304
        $this->assertInLogs('Executing command:');
305
    }
306
307
    /**
308
     * Tests the output file functionality
309
     */
310
    public function testOutput()
311
    {
312
313
        // Getting a temp. file
314
        $tempfile = tempnam(FileUtils::getTempDir(), 'phing-exectest-');
315
316
        // Setting the property
317
        $this->project->setProperty('execTmpFile', $tempfile);
318
        $this->executeTarget(__FUNCTION__);
319
320
        // Validating the output
321
        $output = @file_get_contents($tempfile);
322
        @unlink($tempfile);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

322
        /** @scrutinizer ignore-unhandled */ @unlink($tempfile);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
323
        $this->assertEquals('outfoo', rtrim($output));
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type false; however, parameter $string of rtrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

323
        $this->assertEquals('outfoo', rtrim(/** @scrutinizer ignore-type */ $output));
Loading history...
324
    }
325
326
    /**
327
     * Tests the error file functionality
328
     *
329
     * @requires OS ^(?:(?!Win).)*$
330
     */
331
    public function testError()
332
    {
333
        // Getting a temp. file
334
        $tempfile = tempnam(FileUtils::getTempDir(), 'phing-exectest-');
335
336
        $scriptFile = getcwd() . "/error_output.sh";
337
        file_put_contents($scriptFile, "echo errfoo 1>&2");
338
        chmod($scriptFile, 0744);
339
340
        // Setting the property
341
        $this->project->setProperty('executable', $scriptFile);
342
        $this->project->setProperty('execTmpFile', $tempfile);
343
        $this->executeTarget(__FUNCTION__);
344
345
        // Validating the output
346
        $output = @file_get_contents($tempfile);
347
        @unlink($tempfile);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

347
        /** @scrutinizer ignore-unhandled */ @unlink($tempfile);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
348
        @unlink($scriptFile);
349
        $this->assertEquals("errfoo", rtrim($output));
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type false; however, parameter $string of rtrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

349
        $this->assertEquals("errfoo", rtrim(/** @scrutinizer ignore-type */ $output));
Loading history...
350
    }
351
352
    /**
353
     * Tests the execution with the background process spawning
354
     *
355
     * @requires OS ^(?:(?!Win).)*$
356
     */
357
    public function testSpawn()
358
    {
359
        // Process
360
        $start = time();
361
        $this->executeTarget(__FUNCTION__);
362
        $end = time();
363
        $this->assertLessThan(
364
            4,
365
            ($end - $start),
366
            'Execution time should be lower than 4 seconds, otherwise spawning did not work'
367
        );
368
    }
369
370
    /**
371
     * Tests the nested arguments specified for the execution
372
     */
373
    public function testNestedArgs()
374
    {
375
        $this->executeTarget(__FUNCTION__);
376
        $this->assertInLogs('echo Hello World');
377
    }
378
379
    /**
380
     * Tests the missing/unspecified executable information
381
     */
382
    public function testMissingExecutable()
383
    {
384
        $this->expectBuildExceptionContaining(__FUNCTION__, __FUNCTION__, 'Please provide "executable" information');
385
    }
386
387
    /**
388
     * Tests the escape functionality for special characters in argument
389
     */
390
    public function testEscapedArg()
391
    {
392
        $this->executeTarget(__FUNCTION__);
393
        $this->assertPropertyEquals('outval', $this->windows ? 'abc$b3 SB' : 'abc$b3!SB');
394
    }
395
396
    /**
397
     * Tests the relative source filenames functionality
398
     *
399
     * @requires OS ^(?:(?!Win).)*$
400
     */
401
    public function testRelativeSourceFilenames()
402
    {
403
        $this->executeTarget(__FUNCTION__);
404
        $this->assertNotInLogs('/etc/');
405
    }
406
407
    /**
408
     * Tests the source filename addition functionality
409
     *
410
     * @requires OS ^(?:(?!Win).)*$
411
     */
412
    public function testSourceFilename()
413
    {
414
        $this->executeTarget(__FUNCTION__);
415
        // As the addsourcefilename is 'off', only the executable should be processed in the execution
416
        $this->assertInLogs('Executing command: ls');
417
    }
418
419
    /**
420
     * Tests the output file append functionality
421
     */
422
    public function testOutputAppend()
423
    {
424
425
        // Getting a temp. file
426
        $tempfile = tempnam(FileUtils::getTempDir(), 'phing-exectest-');
427
428
        // Setting the property
429
        $this->project->setProperty('execTmpFile', $tempfile);
430
        $this->executeTarget(__FUNCTION__);
431
432
        // Validating the output
433
        $output = @file_get_contents($tempfile);
434
        @unlink($tempfile);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

434
        /** @scrutinizer ignore-unhandled */ @unlink($tempfile);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
435
        $this->assertEquals($this->windows ? "Append OK \r\nAppend OK" : "Append OK\nAppend OK", rtrim($output));
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type false; however, parameter $string of rtrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

435
        $this->assertEquals($this->windows ? "Append OK \r\nAppend OK" : "Append OK\nAppend OK", rtrim(/** @scrutinizer ignore-type */ $output));
Loading history...
436
    }
437
438
    /**
439
     * Tests the parallel configuration
440
     */
441
    public function testParallel()
442
    {
443
        $this->executeTarget(__FUNCTION__);
444
        $messages = [];
445
        foreach ($this->logBuffer as $log) {
446
            $messages[] = $log['message'];
447
        }
448
        $this->assertEquals(1, substr_count(implode("\n", $messages), 'Executing command:'));
449
    }
450
451
    public function testMapperSupport()
452
    {
453
        // Getting a temp. file
454
        $tempfile = tempnam(FileUtils::getTempDir(), 'phing-exectest-');
455
456
        // Setting the property
457
        $this->project->setProperty('execTmpFile', $tempfile);
458
459
        $this->executeTarget(__FUNCTION__);
460
        $messages = [];
461
        foreach ($this->logBuffer as $log) {
462
            $messages[] = $log['message'];
463
        }
464
        $this->assertContains('Applied echo to 4 files and 0 directories.', $messages);
465
    }
466
467
468
    /**********************************************************************************/
469
    /************************** H E L P E R  M E T H O D S ****************************/
470
    /**********************************************************************************/
471
472
    /**
473
     * @param string $name
474
     * @return Target
475
     * @throws Exception
476
     */
477
    protected function getTargetByName($name)
478
    {
479
        foreach ($this->project->getTargets() as $target) {
480
            if ($target->getName() == $name) {
481
                return $target;
482
            }
483
        }
484
        throw new Exception(sprintf('Target "%s" not found', $name));
485
    }
486
487
    /**
488
     * @param string $target
489
     * @param string $taskName
490
     * @param int $pos
491
     * @return Task
492
     * @throws Exception
493
     */
494
    protected function getTaskFromTarget($target, $taskName, $pos = 0)
495
    {
496
        $rchildren = new ReflectionProperty(get_class($target), 'children');
0 ignored issues
show
Bug introduced by
$target of type string is incompatible with the type object expected by parameter $object of get_class(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

496
        $rchildren = new ReflectionProperty(get_class(/** @scrutinizer ignore-type */ $target), 'children');
Loading history...
497
        $rchildren->setAccessible(true);
498
        $n = -1;
499
        foreach ($rchildren->getValue($target) as $child) {
0 ignored issues
show
Bug introduced by
$target of type string is incompatible with the type null|object expected by parameter $object of ReflectionProperty::getValue(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

499
        foreach ($rchildren->getValue(/** @scrutinizer ignore-type */ $target) as $child) {
Loading history...
500
            if ($child instanceof Task && ++$n == $pos) {
501
                return $child;
502
            }
503
        }
504
505
        throw new Exception(sprintf('%s #%d not found in task', $taskName, $pos));
506
    }
507
508
    /**
509
     * @param string $target
510
     * @param string $task
511
     * @return Task
512
     */
513
    protected function getConfiguredTask($target, $task)
514
    {
515
        $target = $this->getTargetByName($target);
516
        $task = $this->getTaskFromTarget($target, $task);
517
        $task->maybeConfigure();
518
519
        if ($task instanceof UnknownElement) {
520
            return $task->getRuntimeConfigurableWrapper()->getProxy();
521
        }
522
523
        return $task;
524
    }
525
526
    /**
527
     * @param string $property
528
     * @param mixed $value
529
     * @param string $propertyName
530
     */
531
    protected function assertAttributeIsSetTo($property, $value, $propertyName = null)
532
    {
533
        $task = $this->getConfiguredTask('testPropertySet' . ucfirst($property), ApplyTask::class);
534
535
        $propertyName = ($propertyName === null) ? $property : $propertyName;
536
        $rprop = new ReflectionProperty(ApplyTask::class, $propertyName);
537
        $rprop->setAccessible(true);
538
        $this->assertEquals($value, $rprop->getValue($task));
539
    }
540
}
541