| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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.analysis.updateContent; | 5 library test.analysis.updateContent; |
| 6 | 6 |
| 7 import 'package:analysis_server/src/constants.dart'; | 7 import 'package:analysis_server/src/constants.dart'; |
| 8 import 'package:analysis_server/src/protocol.dart'; | 8 import 'package:analysis_server/src/protocol.dart'; |
| 9 import 'package:analysis_server/src/services/index/index.dart'; | 9 import 'package:analysis_server/src/services/index/index.dart'; |
| 10 import 'package:analyzer/src/generated/ast.dart'; | 10 import 'package:analyzer/src/generated/ast.dart'; |
| 11 import 'package:typed_mock/typed_mock.dart'; | 11 import 'package:typed_mock/typed_mock.dart'; |
| 12 import 'package:unittest/unittest.dart'; | 12 import 'package:unittest/unittest.dart'; |
| 13 | 13 |
| 14 import '../analysis_abstract.dart'; | 14 import '../analysis_abstract.dart'; |
| 15 import '../reflective_tests.dart'; | 15 import '../reflective_tests.dart'; |
| 16 | 16 |
| 17 | |
| 18 main() { | 17 main() { |
| 19 groupSep = ' | '; | 18 groupSep = ' | '; |
| 20 runReflectiveTests(UpdateContentTest); | 19 runReflectiveTests(UpdateContentTest); |
| 21 } | 20 } |
| 22 | 21 |
| 23 | |
| 24 compilationUnitMatcher(String file) { | 22 compilationUnitMatcher(String file) { |
| 25 return new _ArgumentMatcher_CompilationUnit(file); | 23 return new _ArgumentMatcher_CompilationUnit(file); |
| 26 } | 24 } |
| 27 | 25 |
| 28 | |
| 29 @reflectiveTest | 26 @reflectiveTest |
| 30 class UpdateContentTest extends AbstractAnalysisTest { | 27 class UpdateContentTest extends AbstractAnalysisTest { |
| 31 Map<String, List<AnalysisError>> filesErrors = {}; | 28 Map<String, List<AnalysisError>> filesErrors = {}; |
| 32 int serverErrorCount = 0; | 29 int serverErrorCount = 0; |
| 33 int navigationCount = 0; | 30 int navigationCount = 0; |
| 34 | 31 |
| 35 Index createIndex() { | 32 Index createIndex() { |
| 36 return new _MockIndex(); | 33 return new _MockIndex(); |
| 37 } | 34 } |
| 38 | 35 |
| 39 @override | 36 @override |
| 40 void processNotification(Notification notification) { | 37 void processNotification(Notification notification) { |
| 41 if (notification.event == ANALYSIS_ERRORS) { | 38 if (notification.event == ANALYSIS_ERRORS) { |
| 42 var decoded = new AnalysisErrorsParams.fromNotification(notification); | 39 var decoded = new AnalysisErrorsParams.fromNotification(notification); |
| 43 filesErrors[decoded.file] = decoded.errors; | 40 filesErrors[decoded.file] = decoded.errors; |
| 44 } | 41 } |
| 45 if (notification.event == ANALYSIS_NAVIGATION) { | 42 if (notification.event == ANALYSIS_NAVIGATION) { |
| 46 navigationCount++; | 43 navigationCount++; |
| 47 } | 44 } |
| 48 if (notification.event == SERVER_ERROR) { | 45 if (notification.event == SERVER_ERROR) { |
| 49 serverErrorCount++; | 46 serverErrorCount++; |
| 50 } | 47 } |
| 51 } | 48 } |
| 52 | 49 |
| 53 test_discardNotifications_onSourceChange() async { | 50 test_discardNotifications_onSourceChange() async { |
| 54 createProject(); | 51 createProject(); |
| 55 addTestFile(''); | 52 addTestFile(''); |
| 56 await server.onAnalysisComplete; | 53 await server.onAnalysisComplete; |
| 57 server.setAnalysisSubscriptions({ | 54 server.setAnalysisSubscriptions( |
| 58 AnalysisService.NAVIGATION: [testFile].toSet() | 55 {AnalysisService.NAVIGATION: [testFile].toSet()}); |
| 59 }); | |
| 60 // update file, analyze, but don't sent notifications | 56 // update file, analyze, but don't sent notifications |
| 61 navigationCount = 0; | 57 navigationCount = 0; |
| 62 server.updateContent('1', { | 58 server.updateContent('1', {testFile: new AddContentOverlay('foo() {}')}); |
| 63 testFile: new AddContentOverlay('foo() {}') | |
| 64 }); | |
| 65 server.test_performAllAnalysisOperations(); | 59 server.test_performAllAnalysisOperations(); |
| 66 expect(serverErrorCount, 0); | 60 expect(serverErrorCount, 0); |
| 67 expect(navigationCount, 0); | 61 expect(navigationCount, 0); |
| 68 // replace the file contents, | 62 // replace the file contents, |
| 69 // should discard any pending notification operations | 63 // should discard any pending notification operations |
| 70 server.updateContent('2', { | 64 server.updateContent('2', {testFile: new AddContentOverlay('bar() {}')}); |
| 71 testFile: new AddContentOverlay('bar() {}') | |
| 72 }); | |
| 73 await server.onAnalysisComplete; | 65 await server.onAnalysisComplete; |
| 74 expect(serverErrorCount, 0); | 66 expect(serverErrorCount, 0); |
| 75 expect(navigationCount, 1); | 67 expect(navigationCount, 1); |
| 76 } | 68 } |
| 77 | 69 |
| 78 test_illegal_ChangeContentOverlay() { | 70 test_illegal_ChangeContentOverlay() { |
| 79 // It should be illegal to send a ChangeContentOverlay for a file that | 71 // It should be illegal to send a ChangeContentOverlay for a file that |
| 80 // doesn't have an overlay yet. | 72 // doesn't have an overlay yet. |
| 81 createProject(); | 73 createProject(); |
| 82 addTestFile('library foo;'); | 74 addTestFile('library foo;'); |
| 83 String id = 'myId'; | 75 String id = 'myId'; |
| 84 try { | 76 try { |
| 85 server.updateContent(id, { | 77 server.updateContent(id, { |
| 86 testFile: new ChangeContentOverlay([new SourceEdit(8, 3, 'bar')]) | 78 testFile: new ChangeContentOverlay([new SourceEdit(8, 3, 'bar')]) |
| 87 }); | 79 }); |
| 88 fail('Expected an exception to be thrown'); | 80 fail('Expected an exception to be thrown'); |
| 89 } on RequestFailure catch (e) { | 81 } on RequestFailure catch (e) { |
| 90 expect(e.response.id, id); | 82 expect(e.response.id, id); |
| 91 expect(e.response.error.code, RequestErrorCode.INVALID_OVERLAY_CHANGE); | 83 expect(e.response.error.code, RequestErrorCode.INVALID_OVERLAY_CHANGE); |
| 92 } | 84 } |
| 93 } | 85 } |
| 94 | 86 |
| 95 test_indexUnitAfterNopChange() async { | 87 test_indexUnitAfterNopChange() async { |
| 96 var testUnitMatcher = compilationUnitMatcher(testFile) as dynamic; | 88 var testUnitMatcher = compilationUnitMatcher(testFile) as dynamic; |
| 97 createProject(); | 89 createProject(); |
| 98 addTestFile('main() { print(1); }'); | 90 addTestFile('main() { print(1); }'); |
| 99 await server.onAnalysisComplete; | 91 await server.onAnalysisComplete; |
| 100 verify(server.index.indexUnit(anyObject, testUnitMatcher)).times(1); | 92 verify(server.index.indexUnit(anyObject, testUnitMatcher)).times(1); |
| 101 // add an overlay | 93 // add an overlay |
| 102 server.updateContent('1', { | 94 server.updateContent( |
| 103 testFile: new AddContentOverlay('main() { print(2); }') | 95 '1', {testFile: new AddContentOverlay('main() { print(2); }')}); |
| 104 }); | |
| 105 // Perform the next single operation: analysis. | 96 // Perform the next single operation: analysis. |
| 106 // It will schedule an indexing operation. | 97 // It will schedule an indexing operation. |
| 107 await server.test_onOperationPerformed; | 98 await server.test_onOperationPerformed; |
| 108 // Update the file and remove an overlay. | 99 // Update the file and remove an overlay. |
| 109 resourceProvider.updateFile(testFile, 'main() { print(2); }'); | 100 resourceProvider.updateFile(testFile, 'main() { print(2); }'); |
| 110 server.updateContent('2', { | 101 server.updateContent('2', {testFile: new RemoveContentOverlay()}); |
| 111 testFile: new RemoveContentOverlay() | |
| 112 }); | |
| 113 // Validate that at the end the unit was indexed. | 102 // Validate that at the end the unit was indexed. |
| 114 await server.onAnalysisComplete; | 103 await server.onAnalysisComplete; |
| 115 verify(server.index.indexUnit(anyObject, testUnitMatcher)).times(2); | 104 verify(server.index.indexUnit(anyObject, testUnitMatcher)).times(2); |
| 116 } | 105 } |
| 117 | 106 |
| 118 test_multiple_contexts() { | 107 test_multiple_contexts() { |
| 119 String fooPath = '/project1/foo.dart'; | 108 String fooPath = '/project1/foo.dart'; |
| 120 resourceProvider.newFile(fooPath, ''' | 109 resourceProvider.newFile(fooPath, ''' |
| 121 library foo; | 110 library foo; |
| 122 import '../project2/baz.dart'; | 111 import '../project2/baz.dart'; |
| 123 main() { f(); }'''); | 112 main() { f(); }'''); |
| 124 String barPath = '/project2/bar.dart'; | 113 String barPath = '/project2/bar.dart'; |
| 125 resourceProvider.newFile(barPath, ''' | 114 resourceProvider.newFile(barPath, ''' |
| 126 library bar; | 115 library bar; |
| 127 import 'baz.dart'; | 116 import 'baz.dart'; |
| 128 main() { f(); }'''); | 117 main() { f(); }'''); |
| 129 String bazPath = '/project2/baz.dart'; | 118 String bazPath = '/project2/baz.dart'; |
| 130 resourceProvider.newFile(bazPath, ''' | 119 resourceProvider.newFile(bazPath, ''' |
| 131 library baz; | 120 library baz; |
| 132 f(int i) {} | 121 f(int i) {} |
| 133 '''); | 122 '''); |
| 134 Request request = new AnalysisSetAnalysisRootsParams( | 123 Request request = new AnalysisSetAnalysisRootsParams( |
| 135 ['/project1', '/project2'], | 124 ['/project1', '/project2'], []).toRequest('0'); |
| 136 []).toRequest('0'); | |
| 137 handleSuccessfulRequest(request); | 125 handleSuccessfulRequest(request); |
| 138 return waitForTasksFinished().then((_) { | 126 return waitForTasksFinished().then((_) { |
| 139 // Files foo.dart and bar.dart should both have errors, since they both | 127 // Files foo.dart and bar.dart should both have errors, since they both |
| 140 // call f() with the wrong number of arguments. | 128 // call f() with the wrong number of arguments. |
| 141 expect(filesErrors[fooPath], hasLength(1)); | 129 expect(filesErrors[fooPath], hasLength(1)); |
| 142 expect(filesErrors[barPath], hasLength(1)); | 130 expect(filesErrors[barPath], hasLength(1)); |
| 143 // Overlay the content of baz.dart to eliminate the errors. | 131 // Overlay the content of baz.dart to eliminate the errors. |
| 144 server.updateContent('1', { | 132 server.updateContent('1', { |
| 145 bazPath: new AddContentOverlay(''' | 133 bazPath: new AddContentOverlay(''' |
| 146 library baz; | 134 library baz; |
| 147 f() {} | 135 f() {} |
| 148 ''') | 136 ''') |
| 149 }); | 137 }); |
| 150 return waitForTasksFinished(); | 138 return waitForTasksFinished(); |
| 151 }).then((_) { | 139 }).then((_) { |
| 152 // The overlay should have been propagated to both contexts, causing both | 140 // The overlay should have been propagated to both contexts, causing both |
| 153 // foo.dart and bar.dart to be reanalyzed and found to be free of errors. | 141 // foo.dart and bar.dart to be reanalyzed and found to be free of errors. |
| 154 expect(filesErrors[fooPath], isEmpty); | 142 expect(filesErrors[fooPath], isEmpty); |
| 155 expect(filesErrors[barPath], isEmpty); | 143 expect(filesErrors[barPath], isEmpty); |
| 156 }); | 144 }); |
| 157 } | 145 } |
| 158 | 146 |
| 159 test_sendNoticesAfterNopChange() async { | 147 test_sendNoticesAfterNopChange() async { |
| 160 createProject(); | 148 createProject(); |
| 161 addTestFile(''); | 149 addTestFile(''); |
| 162 await server.onAnalysisComplete; | 150 await server.onAnalysisComplete; |
| 163 // add an overlay | 151 // add an overlay |
| 164 server.updateContent('1', { | 152 server.updateContent( |
| 165 testFile: new AddContentOverlay('main() {} main() {}') | 153 '1', {testFile: new AddContentOverlay('main() {} main() {}')}); |
| 166 }); | |
| 167 await server.onAnalysisComplete; | 154 await server.onAnalysisComplete; |
| 168 // clear errors and make a no-op change | 155 // clear errors and make a no-op change |
| 169 filesErrors.clear(); | 156 filesErrors.clear(); |
| 170 server.updateContent('2', { | 157 server.updateContent('2', { |
| 171 testFile: new ChangeContentOverlay([new SourceEdit(0, 4, 'main')]) | 158 testFile: new ChangeContentOverlay([new SourceEdit(0, 4, 'main')]) |
| 172 }); | 159 }); |
| 173 await server.onAnalysisComplete; | 160 await server.onAnalysisComplete; |
| 174 // errors should have been resent | 161 // errors should have been resent |
| 175 expect(filesErrors, isNotEmpty); | 162 expect(filesErrors, isNotEmpty); |
| 176 } | 163 } |
| 177 | 164 |
| 178 test_sendNoticesAfterNopChange_flushedUnit() async { | 165 test_sendNoticesAfterNopChange_flushedUnit() async { |
| 179 createProject(); | 166 createProject(); |
| 180 addTestFile(''); | 167 addTestFile(''); |
| 181 await server.onAnalysisComplete; | 168 await server.onAnalysisComplete; |
| 182 // add an overlay | 169 // add an overlay |
| 183 server.updateContent('1', { | 170 server.updateContent( |
| 184 testFile: new AddContentOverlay('main() {} main() {}') | 171 '1', {testFile: new AddContentOverlay('main() {} main() {}')}); |
| 185 }); | |
| 186 await server.onAnalysisComplete; | 172 await server.onAnalysisComplete; |
| 187 // clear errors and make a no-op change | 173 // clear errors and make a no-op change |
| 188 filesErrors.clear(); | 174 filesErrors.clear(); |
| 189 server.test_flushResolvedUnit(testFile); | 175 server.test_flushResolvedUnit(testFile); |
| 190 server.updateContent('2', { | 176 server.updateContent('2', { |
| 191 testFile: new ChangeContentOverlay([new SourceEdit(0, 4, 'main')]) | 177 testFile: new ChangeContentOverlay([new SourceEdit(0, 4, 'main')]) |
| 192 }); | 178 }); |
| 193 await server.onAnalysisComplete; | 179 await server.onAnalysisComplete; |
| 194 // errors should have been resent | 180 // errors should have been resent |
| 195 expect(filesErrors, isNotEmpty); | 181 expect(filesErrors, isNotEmpty); |
| 196 } | 182 } |
| 197 } | 183 } |
| 198 | 184 |
| 199 | |
| 200 class _ArgumentMatcher_CompilationUnit extends ArgumentMatcher { | 185 class _ArgumentMatcher_CompilationUnit extends ArgumentMatcher { |
| 201 final String file; | 186 final String file; |
| 202 | 187 |
| 203 _ArgumentMatcher_CompilationUnit(this.file); | 188 _ArgumentMatcher_CompilationUnit(this.file); |
| 204 | 189 |
| 205 @override | 190 @override |
| 206 bool matches(arg) { | 191 bool matches(arg) { |
| 207 return arg is CompilationUnit && arg.element.source.fullName == file; | 192 return arg is CompilationUnit && arg.element.source.fullName == file; |
| 208 } | 193 } |
| 209 } | 194 } |
| 210 | 195 |
| 211 | |
| 212 class _MockIndex extends TypedMock implements Index { | 196 class _MockIndex extends TypedMock implements Index { |
| 213 noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); | 197 noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); |
| 214 } | 198 } |
| OLD | NEW |