Commit | Line | Data |
---|---|---|
b233b6ca TO |
1 | #!/usr/bin/env php |
2 | <?php | |
3 | ||
4 | require_once dirname(__DIR__) . '/src/Mixlib.php'; | |
5 | ||
6 | function mixlib(): Mixlib { | |
7 | static $mixlib; | |
8 | if ($mixlib === NULL) { | |
9 | $mixlib = new Mixlib(); | |
10 | } | |
11 | return $mixlib; | |
12 | } | |
13 | ||
14 | /** | |
15 | * Generate a test extension which uses the given mixins.. | |
16 | * | |
17 | * @param array $options | |
18 | * Ex: ['force' => TRUE] | |
19 | * @param string $targetDir | |
20 | * The output directory. | |
21 | * @param mixed ...$mixinNames | |
22 | * List of mixins to include in the generated extension. | |
23 | * @return string | |
24 | */ | |
25 | function task_create(array $options, string $targetDir, ...$mixinNames) { | |
26 | if (file_exists($targetDir)) { | |
27 | if (!empty($options['force'])) { | |
28 | fprintf(STDOUT, "Remove %s\n", $targetDir); | |
29 | remove_dir($targetDir); | |
30 | } | |
31 | else { | |
32 | throw new \RuntimeException("Cannot overwrite $targetDir"); | |
33 | } | |
34 | } | |
35 | ||
36 | $mixinNames = resolve_mixin_names($mixinNames); | |
37 | fprintf(STDOUT, "Create %s for %s\n", $targetDir, implode(',', $mixinNames)); | |
38 | ||
39 | $srcDirs = []; | |
40 | $srcDirs[] = mixer_shimmy_dir(); | |
41 | foreach ($mixinNames as $mixinName) { | |
42 | if (is_dir(mixer_mixlib_dir() . "/$mixinName/example")) { | |
43 | $srcDirs[] = mixer_mixlib_dir() . "/$mixinName/example"; | |
44 | } | |
45 | } | |
46 | deep_copy($srcDirs, $targetDir); | |
47 | ||
48 | $mixins = []; | |
49 | foreach ($mixinNames as $mixinName) { | |
50 | $mixins[$mixinName] = mixlib()->assertValid(mixlib()->get($mixinName)); | |
51 | } | |
52 | ||
53 | if (empty($options['bare'])) { | |
54 | mkdir("$targetDir/mixin"); | |
55 | file_put_contents("$targetDir/mixin/polyfill.php", mixlib()->get('polyfill')['src']); | |
56 | foreach ($mixins as $mixin) { | |
57 | file_put_contents("$targetDir/mixin/{$mixin['mixinName']}@{$mixin['mixinVersion']}.mixin.php", $mixin['src']); | |
58 | } | |
59 | } | |
60 | ||
61 | rename(assert_file("$targetDir/info.xml.template"), "$targetDir/info.xml"); | |
62 | update_xml("$targetDir/info.xml", function (SimpleXMLElement $info) use ($mixins) { | |
63 | $mixinsXml = $info->addChild('mixins'); | |
64 | foreach ($mixins as $mixinName => $mixin) { | |
65 | $mixinsXml->addChild('mixin', $mixin['mixinName'] . '@' . $mixin['mixinVersion']); | |
66 | } | |
67 | if (!empty($options['bare'])) { | |
68 | // If the example doesn't include mixins, then we must get them from elsewhere. | |
69 | $requiresXml = $info->addChild('requires'); | |
70 | $requiresXml->addChild('ext', 'mixinlib'); | |
71 | } | |
72 | }); | |
73 | ||
74 | return $targetDir; | |
75 | } | |
76 | ||
77 | /** | |
78 | * Generate and test an extension that uses the given mixins.. | |
79 | * | |
80 | * @param array $options | |
81 | * Ex: ['force' => TRUE] | |
82 | * Ex: ['isolate' => TRUE] | |
83 | * @param string $targetDir | |
84 | * The output directory. | |
85 | * @param mixed ...$mixinNames | |
86 | * List of mixins to include in the generated extension. | |
87 | */ | |
88 | function task_test(array $options, string $targetDir, ...$args) { | |
89 | if (($split = array_search('--', $args)) !== FALSE) { | |
90 | $mixinNames = array_slice($args, 0, $split); | |
91 | $phpunitArgs = array_slice($args, $split + 1); | |
92 | } | |
93 | else { | |
94 | $mixinNames = $args; | |
95 | $phpunitArgs = explode(' ', '--group e2e --debug --stop-on-failure'); | |
96 | } | |
97 | ||
98 | $mixinNames = resolve_mixin_names($mixinNames); | |
99 | $errors = []; | |
100 | if (!empty($options['isolate']) && count($mixinNames) > 1) { | |
101 | foreach ($mixinNames as $mixinName) { | |
102 | try { | |
103 | task_test($options + ['force' => TRUE], $targetDir, $mixinName, '--', ...$phpunitArgs); | |
104 | } | |
105 | catch (\Throwable $t) { | |
106 | fprintf(STDERR, "Error testing $mixinName\n%s\n\n", $t->getTraceAsString()); | |
107 | $errors = [$mixinName]; | |
108 | } | |
109 | } | |
110 | if ($errors) { | |
111 | fprintf(STDERR, "Error processing mixins: %s\n", implode(' ', $errors)); | |
112 | exit(1); | |
113 | } | |
114 | return; | |
115 | } | |
116 | ||
117 | if (is_dir($targetDir) || !empty($options['force'])) { | |
118 | $targetDir = task_create($options, $targetDir, ...$mixinNames); | |
119 | } | |
120 | if (empty(glob("$targetDir/tests/mixin/*.php"))) { | |
121 | fprintf(STDERR, "Skip. No tests found for %s\n", implode(',', $mixinNames)); | |
122 | return; | |
123 | } | |
124 | fprintf(STDOUT, "Test %s\n", implode(',', $mixinNames)); | |
125 | with_dir($targetDir, function () use ($phpunitArgs) { | |
126 | phpunit($phpunitArgs); | |
127 | }); | |
128 | } | |
129 | ||
130 | function task_list(array $options, ...$mixinNames) { | |
131 | $mixinNames = resolve_mixin_names($mixinNames); | |
132 | foreach ($mixinNames as $mixinName) { | |
133 | $mixin = mixlib()->get($mixinName); | |
134 | fprintf(STDOUT, "%-20s %-10s %s\n", $mixin['mixinName'], $mixin['mixinVersion'] ?? '', $mixin['description'] ?? ''); | |
135 | } | |
136 | } | |
137 | ||
138 | function task_help(array $options) { | |
139 | $cmd = basename($GLOBALS['argv'][0]); | |
140 | fprintf(STDERR, "%s - Test utility for extension mixins\n", $cmd); | |
141 | fprintf(STDERR, "\n"); | |
142 | fprintf(STDERR, "Usage:\n"); | |
143 | fprintf(STDERR, " %s create [-f] [--bare] <new-ext-path> [<mixin-name>...]\n", $cmd); | |
144 | fprintf(STDERR, " %s test [-f] [--bare] [--isolate] <new-ext-path> [<mixin-name>...] -- [<phpunit-args>...]\n", $cmd); | |
145 | fprintf(STDERR, " %s list [<mixin-name>...]\n", $cmd); | |
146 | } | |
147 | ||
148 | function mixer_mixlib_dir(): string { | |
149 | return dirname(__DIR__, 3) . '/mixin'; | |
150 | } | |
151 | ||
152 | function mixer_shimmy_dir(): string { | |
153 | return dirname(__DIR__, 3) . '/tests/extensions/shimmy'; | |
154 | } | |
155 | ||
156 | function assert_dir(string $dir): string { | |
157 | if (!file_exists($dir) || !is_dir($dir)) { | |
158 | throw new \RuntimeException("Directory does not exist ($dir)"); | |
159 | } | |
160 | return $dir; | |
161 | } | |
162 | ||
163 | function assert_file(string $file): string { | |
164 | if (!file_exists($file)) { | |
165 | throw new \RuntimeException("File does not exist ($file)"); | |
166 | } | |
167 | return $file; | |
168 | } | |
169 | ||
170 | function remove_dir(string $dir): void { | |
171 | if (file_exists($dir)) { | |
172 | passthru_ok("rm -rf " . escapeshellarg($dir)); | |
173 | } | |
174 | } | |
175 | ||
176 | function deep_copy(array $srcDirs, string $targetDir): void { | |
177 | $srcDirs = (array) $srcDirs; | |
178 | foreach ($srcDirs as $srcDir) { | |
179 | assert_dir($srcDir); | |
180 | } | |
181 | ||
182 | if (!file_exists($targetDir)) { | |
183 | mkdir($targetDir); | |
184 | } | |
185 | ||
186 | foreach ($srcDirs as $srcDir) { | |
187 | passthru_ok(sprintf('rsync -a %s/./ %s/./', escapeshellarg($srcDir), escapeshellarg($targetDir))); | |
188 | } | |
189 | } | |
190 | ||
191 | function phpunit(array $args = []) { | |
192 | $argString = implode(' ' , array_map('escapeshellarg', $args)); | |
193 | passthru_ok('phpunit8 ' . $argString); | |
194 | } | |
195 | ||
196 | function passthru_ok($cmd) { | |
197 | passthru($cmd, $return); | |
198 | if ($return !== 0) { | |
199 | throw new \RuntimeException("Command failed ($cmd)"); | |
200 | } | |
201 | } | |
202 | ||
203 | function resolve_mixin_names(array $mixinNames): array { | |
204 | if (empty($mixinNames)) { | |
205 | return mixlib()->getList(); | |
206 | } | |
207 | else { | |
208 | return array_map( | |
209 | function (string $mixinName) { | |
210 | return trim($mixinName, '/' . DIRECTORY_SEPARATOR); | |
211 | }, $mixinNames); | |
212 | } | |
213 | } | |
214 | ||
215 | function with_dir(string $dir, callable $callback) { | |
216 | assert_dir($dir); | |
217 | $orig = getcwd(); | |
218 | try { | |
219 | chdir($dir); | |
220 | $callback(); | |
221 | } finally { | |
222 | chdir($orig); | |
223 | } | |
224 | } | |
225 | ||
226 | function update_xml(string $file, callable $filter): void { | |
227 | $dom = new DomDocument(); | |
228 | $dom->load($file); | |
229 | $dom->xinclude(); | |
230 | $simpleXml = simplexml_import_dom($dom); | |
231 | $filter($simpleXml); | |
232 | // force pretty printing with encode/decode cycle | |
233 | $outXML = $simpleXml->saveXML(); | |
234 | $xml = new DOMDocument(); | |
235 | $xml->encoding = 'iso-8859-1'; | |
236 | $xml->preserveWhiteSpace = FALSE; | |
237 | $xml->formatOutput = TRUE; | |
238 | $xml->loadXML($outXML); | |
239 | file_put_contents($file, $xml->saveXML()); | |
240 | } | |
241 | ||
242 | function main($args) { | |
243 | $cmd = array_shift($args); | |
244 | ||
245 | $isParsingOptions = TRUE; | |
246 | $newOptions = $newArgs = []; | |
247 | $task = NULL; | |
248 | foreach ($args as $arg) { | |
249 | if (!$isParsingOptions) { | |
250 | $newArgs[] = $arg; | |
251 | } | |
252 | elseif ($arg === '--') { | |
253 | $isParsingOptions = FALSE; | |
254 | $newArgs[] = $arg; | |
255 | } | |
256 | elseif ($arg === '-f') { | |
257 | $newOptions['force'] = TRUE; | |
258 | } | |
259 | elseif (preg_match(';^--(bare|isolate|force)$;', $arg, $m)) { | |
260 | $newOptions[$m[1]] = TRUE; | |
261 | } | |
262 | elseif ($task === NULL) { | |
263 | $task = 'task_' . preg_replace(';[^\w];', '_', $arg); | |
264 | } | |
265 | else { | |
266 | $newArgs[] = $arg; | |
267 | } | |
268 | } | |
269 | ||
270 | if (function_exists($task)) { | |
271 | call_user_func($task, $newOptions, ...$newArgs); | |
272 | } | |
273 | else { | |
274 | task_help([]); | |
275 | exit(1); | |
276 | } | |
277 | } | |
278 | ||
279 | main($argv); |