OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 import 'dart:async' show | |
6 Future; | |
7 | |
8 import 'dart:convert' show | |
9 LineSplitter, | |
10 UTF8; | |
11 | |
12 import 'dart:io' show | |
13 Directory, | |
14 File, | |
15 FileSystemEntity, | |
16 Platform, | |
17 Process, | |
18 ProcessResult; | |
19 | |
20 import 'package:expect/expect.dart' show | |
21 Expect; | |
22 | |
23 import 'multiple_services/multiple_services_tests.dart' as multiple; | |
24 import '../../samples/todomvc/todomvc_service_tests.dart' as todomvc; | |
25 import '../../samples/simple_todo/simple_todo_service_tests.dart' | |
26 as simple_todo; | |
27 | |
28 import '../dartino_compiler/run.dart' show | |
29 export; | |
30 | |
31 import 'package:servicec/compiler.dart' as servicec; | |
32 | |
33 List<ServiceTest> SERVICE_TESTS = <ServiceTest>[] | |
34 ..add(todomvc.serviceTest) | |
35 ..addAll(simple_todo.serviceTests) | |
36 ..addAll(multiple.serviceTests) | |
37 ..addAll(buildStandardServiceTests( | |
38 'conformance', | |
39 ccSources: [ | |
40 'conformance_test.cc', | |
41 'conformance_test_shared.cc', | |
42 ], | |
43 javaSources: [ | |
44 'java/ConformanceTest.java', | |
45 'java/DebugRunner.java', | |
46 'java/SnapshotRunner.java', | |
47 ], | |
48 javaMainClass: 'ConformanceTest')) | |
49 ..addAll(buildStandardServiceTests( | |
50 'performance', | |
51 ccSources: [ | |
52 'performance_test.cc', | |
53 ], | |
54 javaSources: [ | |
55 'java/PerformanceTest.java', | |
56 'java/SnapshotRunner.java', | |
57 ], | |
58 javaMainClass: 'PerformanceTest')); | |
59 | |
60 const String thisDirectory = 'tests/service_tests'; | |
61 | |
62 /// Absolute path to the build directory used by test.py. | |
63 const String buildDirectory = | |
64 const String.fromEnvironment('test.dart.build-dir'); | |
65 | |
66 /// Build architecture provided to test.py | |
67 const String buildArchitecture = | |
68 const String.fromEnvironment('test.dart.build-arch'); | |
69 | |
70 /// Use clang as configured by test.py | |
71 const bool buildClang = | |
72 const bool.fromEnvironment('test.dart.build-clang'); | |
73 | |
74 /// Host system configuration from test.py | |
75 const String buildSystem = | |
76 const String.fromEnvironment('test.dart.build-system'); | |
77 | |
78 /// Use asan as configured by test.py | |
79 const bool buildAsan = | |
80 const bool.fromEnvironment('test.dart.build-asan'); | |
81 | |
82 /// servicec directory as configured by test.py. | |
83 final Uri servicecDirectory = | |
84 new Uri(path: const String.fromEnvironment('test.dart.servicec-dir')); | |
85 | |
86 /// Resources directory for servicec. | |
87 final Uri resourcesDirectory = servicecDirectory.resolve('lib/src/resources'); | |
88 | |
89 /// Temporary directory for test output. | |
90 const String tempTestOutputDirectory = | |
91 const String.fromEnvironment("test.dart.temp-dir"); | |
92 | |
93 final String generatedDirectory = '$tempTestOutputDirectory/service_tests'; | |
94 | |
95 // TODO(zerny): Provide the below constants via configuration from test.py | |
96 final String dartinoExecutable = '$buildDirectory/dartino'; | |
97 final String dartinoLibrary = '$buildDirectory/libdartino.a'; | |
98 | |
99 /// Location of JDK installation. Empty if no installation was found. | |
100 const String javaHome = const String.fromEnvironment('java-home'); | |
101 | |
102 const bool isAsan = buildAsan; | |
103 const bool isClang = buildClang; | |
104 const bool isGNU = !buildClang; | |
105 const bool isMacOS = buildSystem == 'macos'; | |
106 const bool isLinux = buildSystem == 'linux'; | |
107 | |
108 abstract class ServiceTest { | |
109 final String name; | |
110 List<Rule> rules = []; | |
111 String get outputDirectory => '$generatedDirectory/$name'; | |
112 | |
113 ServiceTest(this.name); | |
114 | |
115 // Prepare the test prior to run. | |
116 Future<Null> prepare(); | |
117 | |
118 Future<Directory> ensureDirectory(String path) { | |
119 return new Directory(path).create(recursive: true); | |
120 } | |
121 | |
122 Future<Null> run() async { | |
123 await ensureDirectory(outputDirectory); | |
124 await prepare(); | |
125 for (Rule rule in rules) { | |
126 await rule.build(); | |
127 } | |
128 } | |
129 } | |
130 | |
131 abstract class StandardServiceTest extends ServiceTest { | |
132 final String baseName; | |
133 | |
134 Iterable<String> ccSources; | |
135 | |
136 StandardServiceTest(name, type, ccSources) | |
137 : super('${name}_${type}'), | |
138 baseName = name { | |
139 this.ccSources = ccSources.map((path) => '$inputDirectory/$path').toList() | |
140 ..add('$outputDirectory/cc/${baseName}_service.cc') | |
141 ..add('$outputDirectory/cc/struct.cc') | |
142 ..add('$outputDirectory/cc/unicode.cc'); | |
143 } | |
144 | |
145 String get inputDirectory => '$thisDirectory/$baseName'; | |
146 | |
147 String get idlPath => '$inputDirectory/${baseName}_service.idl'; | |
148 String get serviceImpl => '${baseName}_service_impl.dart'; | |
149 String get servicePath => '$outputDirectory/$serviceImpl'; | |
150 String get snapshotPath => '$outputDirectory/${baseName}.snapshot'; | |
151 String get executablePath => '$outputDirectory/service_${baseName}_test'; | |
152 | |
153 void prepareService() { | |
154 rules.add(new CompileServiceRule(idlPath, outputDirectory)); | |
155 } | |
156 | |
157 void prepareSnapshot() { | |
158 rules.add(new CopyRule(inputDirectory, outputDirectory, [serviceImpl])); | |
159 rules.add(new BuildSnapshotRule(servicePath, snapshotPath)); | |
160 } | |
161 } | |
162 | |
163 class StandardServiceCcTest extends StandardServiceTest { | |
164 StandardServiceCcTest(name, ccSources) | |
165 : super(name, 'cc', ccSources); | |
166 | |
167 Future<Null> prepare() async { | |
168 prepareService(); | |
169 prepareSnapshot(); | |
170 rules.add(new CcRule( | |
171 executable: executablePath, | |
172 includePaths: [outputDirectory], | |
173 sources: ccSources)); | |
174 rules.add(new RunSnapshotRule(executablePath, snapshotPath)); | |
175 } | |
176 } | |
177 | |
178 class StandardServiceJavaTest extends StandardServiceTest { | |
179 final String mainClass; | |
180 Iterable<String> javaSources; | |
181 | |
182 StandardServiceJavaTest(name, this.mainClass, this.javaSources, ccSources) | |
183 : super(name, 'java', ccSources); | |
184 | |
185 String get javaDirectory => '$outputDirectory/java'; | |
186 String get classesDirectory => '$outputDirectory/classes'; | |
187 String get jarFile => '$outputDirectory/$baseName.jar'; | |
188 | |
189 Future<Null> prepare() async { | |
190 prepareService(); | |
191 prepareSnapshot(); | |
192 | |
193 // Complete the test here if Java home is not set. | |
194 if (javaHome.isEmpty) return; | |
195 | |
196 rules.add(new CcRule( | |
197 sharedLibrary: '$outputDirectory/libdartino', | |
198 includePaths: [ | |
199 'include', | |
200 '$javaHome/include', | |
201 '$javaHome/include/${isMacOS ? "darwin" : "linux"}', | |
202 outputDirectory, | |
203 ], | |
204 sources: [ | |
205 '$javaDirectory/jni/dartino_api_wrapper.cc', | |
206 '$javaDirectory/jni/dartino_service_api_wrapper.cc', | |
207 '$javaDirectory/jni/${baseName}_service_wrapper.cc', | |
208 ]..addAll(ccSources))); | |
209 | |
210 rules.add(new MakeDirectoryRule(classesDirectory)); | |
211 | |
212 rules.add(new JavacRule( | |
213 warningAsError: false, | |
214 sources: ['$javaDirectory/dartino'] | |
215 ..addAll(javaSources.map((path) => '$inputDirectory/$path')), | |
216 outputDirectory: classesDirectory)); | |
217 | |
218 rules.add(new JarRule( | |
219 jarFile, | |
220 sources: ['.'], | |
221 baseDirectory: classesDirectory)); | |
222 | |
223 rules.add(new JavaRule( | |
224 mainClass, | |
225 arguments: [snapshotPath], | |
226 classpath: [jarFile], | |
227 libraryPath: outputDirectory)); | |
228 } | |
229 } | |
230 | |
231 List<ServiceTest> buildStandardServiceTests( | |
232 name, | |
233 {ccSources: const <String>[], | |
234 javaSources: const <String>[], | |
235 javaMainClass}) { | |
236 return <ServiceTest>[ | |
237 new StandardServiceCcTest(name, ccSources), | |
238 new StandardServiceJavaTest(name, javaMainClass, javaSources, ccSources), | |
239 ]; | |
240 } | |
241 | |
242 abstract class Rule { | |
243 | |
244 Future<Null> build(); | |
245 | |
246 static Future<int> runCommand( | |
247 String executable, | |
248 List<String> arguments, | |
249 [Map<String,String> environment]) async { | |
250 String environmentString = ''; | |
251 if (environment != null) { | |
252 environmentString = | |
253 environment.keys.map((k) => '$k=${environment[k]}').join(' '); | |
254 } | |
255 String cmdString = "$environmentString $executable ${arguments.join(' ')}"; | |
256 print('Running: $cmdString'); | |
257 | |
258 Process process = | |
259 await Process.start(executable, arguments, environment: environment); | |
260 | |
261 Future stdout = process.stdout.transform(UTF8.decoder) | |
262 .transform(new LineSplitter()) | |
263 .listen(printOut) | |
264 .asFuture() | |
265 .then((_) => print('stdout done')); | |
266 | |
267 Future stderr = process.stderr.transform(UTF8.decoder) | |
268 .transform(new LineSplitter()) | |
269 .listen(printErr) | |
270 .asFuture() | |
271 .then((_) => print('stderr done')); | |
272 | |
273 process.stdin.close(); | |
274 print('stdin closed'); | |
275 | |
276 int exitCode = await process.exitCode; | |
277 print('$cmdString exited with $exitCode'); | |
278 | |
279 await stdout; | |
280 await stderr; | |
281 | |
282 Expect.equals(0, exitCode); | |
283 return exitCode; | |
284 } | |
285 | |
286 static void printOut(String message) { | |
287 print("stdout: $message"); | |
288 } | |
289 | |
290 static void printErr(String message) { | |
291 print("stderr: $message"); | |
292 } | |
293 } | |
294 | |
295 class MakeDirectoryRule extends Rule { | |
296 final String directory; | |
297 final bool recursive; | |
298 | |
299 MakeDirectoryRule(this.directory, {this.recursive: false}); | |
300 | |
301 Future<Null> build() async { | |
302 await new Directory(directory).create(recursive: recursive); | |
303 } | |
304 } | |
305 | |
306 class CopyRule extends Rule { | |
307 String src; | |
308 String dst; | |
309 Iterable<String> files; | |
310 | |
311 CopyRule(this.src, this.dst, this.files); | |
312 | |
313 Future<Null> build() async { | |
314 Directory dstDir = new Directory(dst); | |
315 if (!await dstDir.exists()) { | |
316 await dstDir.create(recursive: true); | |
317 } | |
318 Uri srcUri = new Uri.directory(src); | |
319 for (String file in files) { | |
320 File srcFile = new File.fromUri(srcUri.resolve(file)); | |
321 if (await srcFile.exists()) { | |
322 await srcFile.copy(dstDir.uri.resolve(file).toFilePath()); | |
323 } else { | |
324 throw "Could not find copy-rule source file '$src'."; | |
325 } | |
326 } | |
327 } | |
328 } | |
329 | |
330 class JarRule extends Rule { | |
331 final String jar = "$javaHome/bin/jar"; | |
332 | |
333 final String jarFile; | |
334 final Iterable<String> sources; | |
335 final String baseDirectory; | |
336 | |
337 JarRule(this.jarFile, { | |
338 this.sources: const <String>[], | |
339 this.baseDirectory | |
340 }); | |
341 | |
342 Future<Null> build() async { | |
343 List<String> arguments = <String>["cf", jarFile]; | |
344 if (baseDirectory != null) { | |
345 arguments.addAll(['-C', baseDirectory]); | |
346 } | |
347 arguments.addAll(sources); | |
348 await Rule.runCommand(jar, arguments); | |
349 } | |
350 } | |
351 | |
352 class JavacRule extends Rule { | |
353 final String javac = "$javaHome/bin/javac"; | |
354 | |
355 final Iterable<String> sources; | |
356 final Iterable<String> classpath; | |
357 final Iterable<String> sourcepath; | |
358 final String outputDirectory; | |
359 final bool warningAsError; | |
360 final bool lint; | |
361 | |
362 JavacRule({ | |
363 this.sources: const <String>[], | |
364 this.classpath, | |
365 this.sourcepath, | |
366 this.outputDirectory, | |
367 this.warningAsError: true, | |
368 this.lint: true | |
369 }); | |
370 | |
371 Future<Null> build() async { | |
372 List<String> arguments = <String>[]; | |
373 | |
374 if (classpath != null) { | |
375 arguments.addAll(['-classpath', classpath.join(':')]); | |
376 } | |
377 | |
378 if (sourcepath != null) { | |
379 arguments.addAll(['-sourcepath', sourcepath.join(':')]); | |
380 } | |
381 | |
382 if (outputDirectory != null) { | |
383 arguments.addAll(['-d', outputDirectory]); | |
384 } | |
385 | |
386 if (warningAsError) arguments.add('-Werror'); | |
387 | |
388 if (lint) arguments.add('-Xlint:all'); | |
389 | |
390 for (String src in sources) { | |
391 if (await FileSystemEntity.isDirectory(src)) { | |
392 arguments.addAll(await new Directory(src) | |
393 .list(recursive: true, followLinks: false) | |
394 .where((entity) => entity is File && entity.path.endsWith('.java')) | |
395 .map((entity) => entity.path) | |
396 .toList()); | |
397 } else { | |
398 arguments.add(src); | |
399 } | |
400 } | |
401 | |
402 await Rule.runCommand(javac, arguments); | |
403 } | |
404 } | |
405 | |
406 class JavaRule extends Rule { | |
407 final String java = "$javaHome/bin/java"; | |
408 | |
409 final String mainClass; | |
410 final Iterable<String> arguments; | |
411 final Iterable<String> classpath; | |
412 final bool enableAssertions; | |
413 final String libraryPath; | |
414 | |
415 JavaRule(this.mainClass, { | |
416 this.arguments: const <String>[], | |
417 this.classpath, | |
418 this.enableAssertions: true, | |
419 this.libraryPath | |
420 }); | |
421 | |
422 Future<Null> build() async { | |
423 List<String> javaArguments = <String>[]; | |
424 Map<String, String> javaEnvironment; | |
425 | |
426 if (buildArchitecture == 'ia32') { | |
427 javaArguments.add('-d32'); | |
428 } else if (buildArchitecture == 'x64') { | |
429 javaArguments.add('-d64'); | |
430 } else { | |
431 throw "Unsupported architecture $buildArchitecture"; | |
432 } | |
433 | |
434 if (enableAssertions) { | |
435 javaArguments.add('-enableassertions'); | |
436 } | |
437 | |
438 if (classpath != null) { | |
439 javaArguments.addAll(['-classpath', classpath.join(':')]); | |
440 } | |
441 | |
442 if (libraryPath != null) { | |
443 javaArguments.add('-Djava.library.path=$libraryPath'); | |
444 javaEnvironment = <String, String>{ 'LD_LIBRARY_PATH': libraryPath }; | |
445 } | |
446 | |
447 javaArguments.add(mainClass); | |
448 | |
449 if (arguments != null) { | |
450 javaArguments.addAll(arguments); | |
451 } | |
452 | |
453 await Rule.runCommand(java, javaArguments, javaEnvironment); | |
454 } | |
455 } | |
456 | |
457 // TODO(zerny): Consider refactoring dartino specifics into a derived class. | |
458 // TODO(zerny): Find a way to obtain the dartino build configuration from gyp. | |
459 class CcRule extends Rule { | |
460 final String language; | |
461 final String executable; | |
462 final String sharedLibrary; | |
463 final Iterable<String> flags; | |
464 final Iterable<String> sources; | |
465 final Iterable<String> libraries; | |
466 final Iterable<String> includePaths; | |
467 final Iterable<String> libraryPaths; | |
468 | |
469 CcRule({ | |
470 this.executable, | |
471 this.sharedLibrary, | |
472 this.language: 'c++11', | |
473 this.flags: const <String>[], | |
474 this.sources: const <String>[], | |
475 this.libraries: const <String>[], | |
476 this.includePaths: const <String>[], | |
477 this.libraryPaths: const <String>[] | |
478 }) { | |
479 if (executable == null && sharedLibrary == null) { | |
480 throw "CcRule expects a valid output path for an executable or library"; | |
481 } | |
482 if (executable != null && sharedLibrary != null) { | |
483 throw "CcRule expects either an executable or a library, not both"; | |
484 } | |
485 } | |
486 | |
487 String get compiler => 'tools/cxx_wrapper.py'; | |
488 | |
489 String get output { | |
490 if (executable != null) return executable; | |
491 String suffix = isMacOS ? 'jnilib' : 'so'; | |
492 return '$sharedLibrary.$suffix'; | |
493 } | |
494 | |
495 void addBuildFlags(List<String> arguments) { | |
496 arguments.add('-std=${language}'); | |
497 arguments.add('-DDARTINO_ENABLE_FFI'); | |
498 arguments.add('-DDARTINO_ENABLE_LIVE_CODING'); | |
499 arguments.add('-DDARTINO_ENABLE_PRINT_INTERCEPTORS'); | |
500 arguments.add('-DDARTINO_ENABLE_NATIVE_PROCESSES'); | |
501 if (sharedLibrary != null) arguments.add('-shared'); | |
502 if (buildArchitecture == 'ia32') { | |
503 arguments.add('-m32'); | |
504 arguments.add('-DDARTINO32'); | |
505 arguments.add('-DDARTINO_TARGET_IA32'); | |
506 } else if (buildArchitecture == 'x64') { | |
507 arguments.add('-m64'); | |
508 arguments.add('-DDARTINO64'); | |
509 arguments.add('-DDARTINO_TARGET_X64'); | |
510 if (sharedLibrary != null) arguments.add('-fPIC'); | |
511 } else { | |
512 throw "Unsupported architecture ${buildArchitecture}"; | |
513 } | |
514 } | |
515 | |
516 void addHostFlags(List<String> arguments) { | |
517 if (isMacOS) { | |
518 arguments.add('-DDARTINO_TARGET_OS_MACOS'); | |
519 arguments.add('-DDARTINO_TARGET_OS_POSIX'); | |
520 arguments.addAll(['-framework', 'CoreFoundation']); | |
521 } else if (isLinux) { | |
522 arguments.add('-DDARTINO_TARGET_OS_LINUX'); | |
523 arguments.add('-DDARTINO_TARGET_OS_POSIX'); | |
524 } else { | |
525 throw "Unsupported host ${buildSystem}"; | |
526 } | |
527 } | |
528 | |
529 void addUserFlags(List<String> arguments) { | |
530 arguments.addAll(flags); | |
531 } | |
532 | |
533 void addIncludePaths(List<String> arguments) { | |
534 arguments.add('-I.'); | |
535 arguments.addAll(includePaths.map((path) => '-I${path}')); | |
536 } | |
537 | |
538 void addLibraryPaths(List<String> arguments) { | |
539 String arch; | |
540 if (buildArchitecture == 'ia32') { | |
541 arch = 'x86'; | |
542 } else if (buildArchitecture == 'x64') { | |
543 arch = 'x64'; | |
544 } else { | |
545 throw "Unsupported host architecture ${buildArchitecture}"; | |
546 } | |
547 if (isMacOS) arguments.add('-Lthird_party/libs/mac/$arch'); | |
548 if (isLinux) arguments.add('-Lthird_party/libs/linux/$arch'); | |
549 arguments.addAll(libraryPaths.map((path) => '-L${path}')); | |
550 } | |
551 | |
552 void addLibraries(List<String> arguments) { | |
553 arguments.add(dartinoLibrary); | |
554 for (String lib in libraries) { | |
555 arguments.add(lib.endsWith('.a') ? lib : '-l$lib'); | |
556 } | |
557 } | |
558 | |
559 void addHostLibraries(List<String> arguments) { | |
560 arguments.addAll([ | |
561 '-lpthread', | |
562 '-ldl', | |
563 '-rdynamic', | |
564 ]); | |
565 } | |
566 | |
567 void addSources(List<String> arguments) { | |
568 arguments.addAll(sources); | |
569 } | |
570 | |
571 Future<Null> build() async { | |
572 List<String> arguments = <String>[]; | |
573 if (isClang) arguments.add('-DDARTINO_CLANG'); | |
574 if (isAsan) { | |
575 arguments.add('-DDARTINO_ASAN'); | |
576 arguments.add('-L/DARTINO_ASAN'); | |
577 } | |
578 addBuildFlags(arguments); | |
579 addHostFlags(arguments); | |
580 addUserFlags(arguments); | |
581 addIncludePaths(arguments); | |
582 addLibraryPaths(arguments); | |
583 arguments.addAll(['-o', output]); | |
584 if (isGNU) arguments.add('-Wl,--start-group'); | |
585 addSources(arguments); | |
586 addLibraries(arguments); | |
587 if (isGNU) arguments.add('-Wl,--end-group'); | |
588 addHostLibraries(arguments); | |
589 await Rule.runCommand(compiler, arguments); | |
590 } | |
591 } | |
592 | |
593 class BuildSnapshotRule extends Rule { | |
594 final String program; | |
595 final String snapshot; | |
596 | |
597 BuildSnapshotRule(this.program, this.snapshot); | |
598 | |
599 Future<Null> build() async { | |
600 await export(program, snapshot); | |
601 } | |
602 } | |
603 | |
604 class RunSnapshotsRule extends Rule { | |
605 final String executable; | |
606 final List<String> snapshots; | |
607 | |
608 RunSnapshotsRule(this.executable, this.snapshots); | |
609 | |
610 Future<Null> build() { | |
611 Map<String, String> env; | |
612 if (isMacOS && isAsan) { | |
613 env = <String, String>{'DYLD_LIBRARY_PATH': buildDirectory}; | |
614 } | |
615 return Rule.runCommand(executable, snapshots, env); | |
616 } | |
617 } | |
618 | |
619 class RunSnapshotRule extends RunSnapshotsRule { | |
620 String get snapshot => snapshots[0]; | |
621 | |
622 RunSnapshotRule(String executable, String snapshot) | |
623 : super(executable, [snapshot]); | |
624 } | |
625 | |
626 class CompileServiceRule extends Rule { | |
627 final String idlFile; | |
628 final String outputDirectory; | |
629 | |
630 CompileServiceRule(this.idlFile, this.outputDirectory); | |
631 | |
632 Future<Null> build() async { | |
633 bool success = await servicec.compileAndReportErrors( | |
634 idlFile, idlFile, resourcesDirectory.path, outputDirectory); | |
635 Expect.isTrue(success); | |
636 } | |
637 } | |
638 | |
639 // Test entry point. | |
640 | |
641 typedef Future NoArgFuture(); | |
642 | |
643 Future<Map<String, NoArgFuture>> listTests() async { | |
644 var tests = <String, NoArgFuture>{}; | |
645 for (ServiceTest test in SERVICE_TESTS) { | |
646 tests['service_tests/${test.name}'] = () => test.run(); | |
647 } | |
648 return tests; | |
649 } | |
OLD | NEW |