Test Failed
Push — master ( e042c7...883f5c )
by Hannes
02:15
created

TestCommand   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 133
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 133
rs 10
c 0
b 0
f 0
wmc 17

3 Methods

Rating   Name   Duplication   Size   Complexity  
B readBootstrap() 0 15 6
A configure() 0 53 1
C execute() 0 52 10
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace hanneskod\readmetester\Console;
6
7
use Symfony\Component\Console\Command\Command;
8
use Symfony\Component\Console\Input\InputArgument;
9
use Symfony\Component\Console\Input\InputOption;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Output\OutputInterface;
12
use hanneskod\readmetester\EngineBuilder;
13
use hanneskod\readmetester\SourceFileIterator;
14
use hanneskod\readmetester\Example\RegexpFilter;
15
use hanneskod\readmetester\Example\UnnamedFilter;
16
use hanneskod\readmetester\Runner\EvalRunner;
17
use hanneskod\readmetester\Runner\ProcessRunner;
18
19
/**
20
 * CLI command to run test
21
 */
22
class TestCommand extends Command
23
{
24
    /**
25
     * Path to default bootstrap file
26
     */
27
    const DEFAULT_BOOTSTRAP = 'vendor/autoload.php';
28
29
    protected function configure()
30
    {
31
        $this->setName('test')
32
            ->setDescription('Test examples in readme file')
33
            ->addArgument(
34
                'source',
35
                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
36
                'One or more files or directories to test',
37
                ['README.md']
38
            )
39
            ->addOption(
40
                'filter',
41
                null,
42
                InputOption::VALUE_REQUIRED,
43
                'Filter which examples to test using a regular expression'
44
            )
45
            ->addOption(
46
                'format',
47
                null,
48
                InputOption::VALUE_REQUIRED,
49
                'Set output format (default or json)',
50
                'default'
51
            )
52
            ->addOption(
53
                'flagged-only',
54
                null,
55
                InputOption::VALUE_NONE,
56
                'Test only examples flagged with the @example annotation'
57
            )
58
            ->addOption(
59
                'ignore-unknown-annotations',
60
                null,
61
                InputOption::VALUE_NONE,
62
                'Ignore example annotations that are not known to readme-tester'
63
            )
64
            ->addOption(
65
                'runner',
66
                null,
67
                InputOption::VALUE_REQUIRED,
68
                'Specify the example runner to use (process or eval)',
69
                'process'
70
            )
71
            ->addOption(
72
                'bootstrap',
73
                null,
74
                InputOption::VALUE_REQUIRED,
75
                'A "bootstrap" PHP file that is run before testing'
76
            )
77
            ->addOption(
78
                'no-auto-bootstrap',
79
                null,
80
                InputOption::VALUE_NONE,
81
                "Don't try to load a local composer autoloader when boostrap is not definied"
82
            )
83
        ;
84
    }
85
86
    protected function execute(InputInterface $input, OutputInterface $output): int
87
    {
88
        $formatter = $input->getOption('format') == 'json'
89
            ? new JsonFormatter($output)
90
            : new DefaultFormatter($output);
91
92
        $formatter->onInvokationStart();
93
94
        $engineBuilder = new EngineBuilder;
95
96
        if ($filter = $input->getOption('filter')) {
97
            $engineBuilder->setFilter(new RegexpFilter($filter));
98
        } elseif ($input->getOption('flagged-only')) {
99
            $engineBuilder->setFilter(new UnnamedFilter);
100
        }
101
102
        if ($bootstrap = $this->readBootstrap($input)) {
103
            $formatter->onBootstrap($bootstrap);
104
        }
105
106
        switch ($input->getOption('runner')) {
107
            case 'process':
108
                $engineBuilder->setRunner(new ProcessRunner($bootstrap));
109
                break;
110
            case 'eval':
111
                $engineBuilder->setRunner(new EvalRunner($bootstrap));
112
                break;
113
            default:
114
                throw new \RuntimeException("Unknown runner '{$input->getOption('runner')}'");
115
        }
116
117
        if ($input->getOption('ignore-unknown-annotations')) {
118
            $engineBuilder->setIgnoreUnknownAnnotations();
119
        }
120
121
        $engine = $engineBuilder->buildEngine();
122
123
        $engine->registerListener($formatter);
124
125
        $exitStatus = new ExitStatusListener;
126
        $engine->registerListener($exitStatus);
127
128
        foreach ($input->getArgument('source') as $source) {
129
            foreach (new SourceFileIterator($source) as $filename => $contents) {
130
                $formatter->onFile($filename);
131
                $engine->testFile($contents);
132
            }
133
        }
134
135
        $formatter->onInvokationEnd();
136
137
        return $exitStatus->getStatusCode();
138
    }
139
140
    private function readBootstrap(InputInterface $input): string
141
    {
142
        if ($filename = $input->getOption('bootstrap')) {
143
            if (!file_exists($filename) || !is_readable($filename)) {
144
                throw new \RuntimeException("Unable to bootstrap $filename");
145
            }
146
147
            return realpath($filename);
148
        }
149
150
        if (!$input->getOption('no-auto-bootstrap') && is_readable(self::DEFAULT_BOOTSTRAP)) {
151
            return realpath(self::DEFAULT_BOOTSTRAP);
152
        }
153
154
        return '';
155
    }
156
}
157