| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library test.integration.analysis; | 5 library test.integration.analysis; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 import 'dart:convert'; | 9 import 'dart:convert'; |
| 10 import 'dart:io'; | 10 import 'dart:io'; |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 45 HashMap<String, List<AnalysisError>> currentAnalysisErrors = | 45 HashMap<String, List<AnalysisError>> currentAnalysisErrors = |
| 46 new HashMap<String, List<AnalysisError>>(); | 46 new HashMap<String, List<AnalysisError>>(); |
| 47 | 47 |
| 48 /** | 48 /** |
| 49 * True if the teardown process should skip sending a "server.shutdown" | 49 * True if the teardown process should skip sending a "server.shutdown" |
| 50 * request (e.g. because the server is known to have already shutdown). | 50 * request (e.g. because the server is known to have already shutdown). |
| 51 */ | 51 */ |
| 52 bool skipShutdown = false; | 52 bool skipShutdown = false; |
| 53 | 53 |
| 54 /** | 54 /** |
| 55 * Data associated with the "server.connected" notification that was received | |
| 56 * when the server started up. | |
| 57 */ | |
| 58 var serverConnectedParams; | |
| 59 | |
| 60 /** | |
| 61 * True if we are currently subscribed to [SERVER_STATUS] updates. | 55 * True if we are currently subscribed to [SERVER_STATUS] updates. |
| 62 */ | 56 */ |
| 63 bool _subscribedToServerStatus = false; | 57 bool _subscribedToServerStatus = false; |
| 64 | 58 |
| 65 AbstractAnalysisServerIntegrationTest() { | 59 AbstractAnalysisServerIntegrationTest() { |
| 66 initializeInttestMixin(); | 60 initializeInttestMixin(); |
| 67 } | 61 } |
| 68 | 62 |
| 69 /** | 63 /** |
| 70 * Write a source file with the given absolute [pathname] and [contents]. | 64 * Write a source file with the given absolute [pathname] and [contents]. |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 149 sourceDirectory = Directory.systemTemp.createTempSync('analysisServer'); | 143 sourceDirectory = Directory.systemTemp.createTempSync('analysisServer'); |
| 150 | 144 |
| 151 onAnalysisErrors.listen((AnalysisErrorsParams params) { | 145 onAnalysisErrors.listen((AnalysisErrorsParams params) { |
| 152 currentAnalysisErrors[params.file] = params.errors; | 146 currentAnalysisErrors[params.file] = params.errors; |
| 153 }); | 147 }); |
| 154 Completer serverConnected = new Completer(); | 148 Completer serverConnected = new Completer(); |
| 155 onServerConnected.listen((_) { | 149 onServerConnected.listen((_) { |
| 156 expect(serverConnected.isCompleted, isFalse); | 150 expect(serverConnected.isCompleted, isFalse); |
| 157 serverConnected.complete(); | 151 serverConnected.complete(); |
| 158 }); | 152 }); |
| 159 return server.start(dispatchNotification).then((params) { | 153 return server.start().then((_) { |
| 160 serverConnectedParams = params; | 154 server.listenToOutput(dispatchNotification); |
| 161 server.exitCode.then((_) { | 155 server.exitCode.then((_) { |
| 162 skipShutdown = true; | 156 skipShutdown = true; |
| 163 }); | 157 }); |
| 164 return serverConnected.future; | 158 return serverConnected.future; |
| 165 }); | 159 }); |
| 166 } | 160 } |
| 167 | 161 |
| 168 /** | 162 /** |
| 169 * After every test, the server is stopped and [sourceDirectory] is deleted. | 163 * After every test, the server is stopped and [sourceDirectory] is deleted. |
| 170 */ | 164 */ |
| (...skipping 510 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 681 } | 675 } |
| 682 pathname = parent; | 676 pathname = parent; |
| 683 } | 677 } |
| 684 return dirname(pathname); | 678 return dirname(pathname); |
| 685 } | 679 } |
| 686 | 680 |
| 687 /** | 681 /** |
| 688 * Start the server. If [debugServer] is true, the server will be started | 682 * Start the server. If [debugServer] is true, the server will be started |
| 689 * with "--debug", allowing a debugger to be attached. | 683 * with "--debug", allowing a debugger to be attached. |
| 690 */ | 684 */ |
| 691 Future start(NotificationProcessor notificationProcessor, {bool debugServer: | 685 Future start({bool debugServer: false}) { |
| 692 false}) { | |
| 693 if (_process != null) { | 686 if (_process != null) { |
| 694 throw new Exception('Process already started'); | 687 throw new Exception('Process already started'); |
| 695 } | 688 } |
| 696 _time.start(); | 689 _time.start(); |
| 697 // TODO(paulberry): move the logic for finding the script, the dart | |
| 698 // executable, and the package root into a shell script. | |
| 699 String dartBinary = Platform.executable; | 690 String dartBinary = Platform.executable; |
| 700 String rootDir = | 691 String rootDir = |
| 701 findRoot(Platform.script.toFilePath(windows: Platform.isWindows)); | 692 findRoot(Platform.script.toFilePath(windows: Platform.isWindows)); |
| 702 String serverPath = normalize(join(rootDir, 'bin', 'server.dart')); | 693 String serverPath = normalize(join(rootDir, 'bin', 'server.dart')); |
| 703 List<String> arguments = []; | 694 List<String> arguments = []; |
| 704 if (debugServer) { | 695 if (debugServer) { |
| 705 arguments.add('--debug'); | 696 arguments.add('--debug'); |
| 706 } | 697 } |
| 707 if (Platform.packageRoot.isNotEmpty) { | 698 if (Platform.packageRoot.isNotEmpty) { |
| 708 arguments.add('--package-root=${Platform.packageRoot}'); | 699 arguments.add('--package-root=${Platform.packageRoot}'); |
| 709 } | 700 } |
| 710 arguments.add('--checked'); | 701 arguments.add('--checked'); |
| 711 arguments.add(serverPath); | 702 arguments.add(serverPath); |
| 712 return Process.start(dartBinary, arguments).then((Process process) { | 703 return Process.start(dartBinary, arguments).then((Process process) { |
| 713 _process = process; | 704 _process = process; |
| 714 process.stdout.transform( | |
| 715 (new Utf8Codec()).decoder).transform(new LineSplitter()).listen((Strin
g line) { | |
| 716 String trimmedLine = line.trim(); | |
| 717 _recordStdio('RECV: $trimmedLine'); | |
| 718 var message; | |
| 719 try { | |
| 720 message = JSON.decoder.convert(trimmedLine); | |
| 721 } catch (exception) { | |
| 722 _badDataFromServer(); | |
| 723 return; | |
| 724 } | |
| 725 expect(message, isMap); | |
| 726 Map messageAsMap = message; | |
| 727 if (messageAsMap.containsKey('id')) { | |
| 728 expect(messageAsMap['id'], isString); | |
| 729 String id = message['id']; | |
| 730 Completer completer = _pendingCommands[id]; | |
| 731 if (completer == null) { | |
| 732 fail('Unexpected response from server: id=$id'); | |
| 733 } else { | |
| 734 _pendingCommands.remove(id); | |
| 735 } | |
| 736 if (messageAsMap.containsKey('error')) { | |
| 737 // TODO(paulberry): propagate the error info to the completer. | |
| 738 completer.completeError( | |
| 739 new UnimplementedError( | |
| 740 'Server responded with an error: ${JSON.encode(message)}')); | |
| 741 } else { | |
| 742 completer.complete(messageAsMap['result']); | |
| 743 } | |
| 744 // Check that the message is well-formed. We do this after calling | |
| 745 // completer.complete() or completer.completeError() so that we don't | |
| 746 // stall the test in the event of an error. | |
| 747 expect(message, isResponse); | |
| 748 } else { | |
| 749 // Message is a notification. It should have an event and possibly | |
| 750 // params. | |
| 751 expect(messageAsMap, contains('event')); | |
| 752 expect(messageAsMap['event'], isString); | |
| 753 notificationProcessor(messageAsMap['event'], messageAsMap['params']); | |
| 754 // Check that the message is well-formed. We do this after calling | |
| 755 // notificationController.add() so that we don't stall the test in the | |
| 756 // event of an error. | |
| 757 expect(message, isNotification); | |
| 758 } | |
| 759 }); | |
| 760 process.stderr.transform( | |
| 761 (new Utf8Codec()).decoder).transform(new LineSplitter()).listen((Strin
g line) { | |
| 762 String trimmedLine = line.trim(); | |
| 763 _recordStdio('ERR: $trimmedLine'); | |
| 764 _badDataFromServer(); | |
| 765 }); | |
| 766 process.exitCode.then((int code) { | 705 process.exitCode.then((int code) { |
| 767 _recordStdio('TERMINATED WITH EXIT CODE $code'); | 706 _recordStdio('TERMINATED WITH EXIT CODE $code'); |
| 768 if (code != 0) { | 707 if (code != 0) { |
| 769 _badDataFromServer(); | 708 _badDataFromServer(); |
| 770 } | 709 } |
| 771 }); | 710 }); |
| 772 }); | 711 }); |
| 773 } | 712 } |
| 774 | 713 |
| 775 /** | 714 /** |
| 715 * Start listening to output from the server, and deliver notifications to |
| 716 * [notificationProcessor]. |
| 717 */ |
| 718 void listenToOutput(NotificationProcessor notificationProcessor) { |
| 719 _process.stdout.transform( |
| 720 (new Utf8Codec()).decoder).transform(new LineSplitter()).listen((String
line) { |
| 721 String trimmedLine = line.trim(); |
| 722 _recordStdio('RECV: $trimmedLine'); |
| 723 var message; |
| 724 try { |
| 725 message = JSON.decoder.convert(trimmedLine); |
| 726 } catch (exception) { |
| 727 _badDataFromServer(); |
| 728 return; |
| 729 } |
| 730 expect(message, isMap); |
| 731 Map messageAsMap = message; |
| 732 if (messageAsMap.containsKey('id')) { |
| 733 expect(messageAsMap['id'], isString); |
| 734 String id = message['id']; |
| 735 Completer completer = _pendingCommands[id]; |
| 736 if (completer == null) { |
| 737 fail('Unexpected response from server: id=$id'); |
| 738 } else { |
| 739 _pendingCommands.remove(id); |
| 740 } |
| 741 if (messageAsMap.containsKey('error')) { |
| 742 // TODO(paulberry): propagate the error info to the completer. |
| 743 completer.completeError( |
| 744 new UnimplementedError( |
| 745 'Server responded with an error: ${JSON.encode(message)}')); |
| 746 } else { |
| 747 completer.complete(messageAsMap['result']); |
| 748 } |
| 749 // Check that the message is well-formed. We do this after calling |
| 750 // completer.complete() or completer.completeError() so that we don't |
| 751 // stall the test in the event of an error. |
| 752 expect(message, isResponse); |
| 753 } else { |
| 754 // Message is a notification. It should have an event and possibly |
| 755 // params. |
| 756 expect(messageAsMap, contains('event')); |
| 757 expect(messageAsMap['event'], isString); |
| 758 notificationProcessor(messageAsMap['event'], messageAsMap['params']); |
| 759 // Check that the message is well-formed. We do this after calling |
| 760 // notificationController.add() so that we don't stall the test in the |
| 761 // event of an error. |
| 762 expect(message, isNotification); |
| 763 } |
| 764 }); |
| 765 _process.stderr.transform( |
| 766 (new Utf8Codec()).decoder).transform(new LineSplitter()).listen((String
line) { |
| 767 String trimmedLine = line.trim(); |
| 768 _recordStdio('ERR: $trimmedLine'); |
| 769 _badDataFromServer(); |
| 770 }); |
| 771 } |
| 772 |
| 773 /** |
| 776 * Future that completes when the server process exits. | 774 * Future that completes when the server process exits. |
| 777 */ | 775 */ |
| 778 Future<int> get exitCode => _process.exitCode; | 776 Future<int> get exitCode => _process.exitCode; |
| 779 | 777 |
| 780 /** | 778 /** |
| 781 * Stop the server. | 779 * Stop the server. |
| 782 */ | 780 */ |
| 783 Future kill() { | 781 Future kill() { |
| 784 debugStdio(); | 782 debugStdio(); |
| 785 _recordStdio('PROCESS FORCIBLY TERMINATED'); | 783 _recordStdio('PROCESS FORCIBLY TERMINATED'); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 820 if (_debuggingStdio) { | 818 if (_debuggingStdio) { |
| 821 return; | 819 return; |
| 822 } | 820 } |
| 823 _debuggingStdio = true; | 821 _debuggingStdio = true; |
| 824 for (String line in _recordedStdio) { | 822 for (String line in _recordedStdio) { |
| 825 print(line); | 823 print(line); |
| 826 } | 824 } |
| 827 } | 825 } |
| 828 | 826 |
| 829 /** | 827 /** |
| 828 * Return a future that will complete when all commands that have been sent |
| 829 * to the server so far have been flushed to the OS buffer. |
| 830 */ |
| 831 Future flushCommands() { |
| 832 return _process.stdin.flush(); |
| 833 } |
| 834 |
| 835 /** |
| 830 * Deal with bad data received from the server. | 836 * Deal with bad data received from the server. |
| 831 */ | 837 */ |
| 832 void _badDataFromServer() { | 838 void _badDataFromServer() { |
| 833 if (_receivedBadDataFromServer) { | 839 if (_receivedBadDataFromServer) { |
| 834 // We're already dealing with it. | 840 // We're already dealing with it. |
| 835 return; | 841 return; |
| 836 } | 842 } |
| 837 _receivedBadDataFromServer = true; | 843 _receivedBadDataFromServer = true; |
| 838 debugStdio(); | 844 debugStdio(); |
| 839 // Give the server 1 second to continue outputting bad data before we kill | 845 // Give the server 1 second to continue outputting bad data before we kill |
| (...skipping 12 matching lines...) Expand all Loading... |
| 852 */ | 858 */ |
| 853 void _recordStdio(String line) { | 859 void _recordStdio(String line) { |
| 854 double elapsedTime = _time.elapsedTicks / _time.frequency; | 860 double elapsedTime = _time.elapsedTicks / _time.frequency; |
| 855 line = "$elapsedTime: $line"; | 861 line = "$elapsedTime: $line"; |
| 856 if (_debuggingStdio) { | 862 if (_debuggingStdio) { |
| 857 print(line); | 863 print(line); |
| 858 } | 864 } |
| 859 _recordedStdio.add(line); | 865 _recordedStdio.add(line); |
| 860 } | 866 } |
| 861 } | 867 } |
| OLD | NEW |