OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, 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 library validator_test; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:io'; | |
9 import 'dart:json' as json; | |
10 import 'dart:math' as math; | |
11 | |
12 import 'package:http/http.dart' as http; | |
13 import 'package:http/testing.dart'; | |
14 import 'package:pathos/path.dart' as path; | |
15 import 'package:scheduled_test/scheduled_test.dart'; | |
16 | |
17 import '../../pub/entrypoint.dart'; | |
18 import '../../pub/io.dart'; | |
19 import '../../pub/validator.dart'; | |
20 import '../../pub/validator/compiled_dartdoc.dart'; | |
21 import '../../pub/validator/dependency.dart'; | |
22 import '../../pub/validator/directory.dart'; | |
23 import '../../pub/validator/lib.dart'; | |
24 import '../../pub/validator/license.dart'; | |
25 import '../../pub/validator/name.dart'; | |
26 import '../../pub/validator/pubspec_field.dart'; | |
27 import '../../pub/validator/size.dart'; | |
28 import '../../pub/validator/utf8_readme.dart'; | |
29 import 'descriptor.dart' as d; | |
30 import 'test_pub.dart'; | |
31 | |
32 void expectNoValidationError(ValidatorCreator fn) { | |
33 expect(schedulePackageValidation(fn), completion(pairOf(isEmpty, isEmpty))); | |
34 } | |
35 | |
36 void expectValidationError(ValidatorCreator fn) { | |
37 expect(schedulePackageValidation(fn), | |
38 completion(pairOf(isNot(isEmpty), anything))); | |
39 } | |
40 | |
41 void expectValidationWarning(ValidatorCreator fn) { | |
42 expect(schedulePackageValidation(fn), | |
43 completion(pairOf(isEmpty, isNot(isEmpty)))); | |
44 } | |
45 | |
46 expectDependencyValidationError(String error) { | |
47 expect(schedulePackageValidation(dependency), | |
48 completion(pairOf(someElement(contains(error)), isEmpty))); | |
49 } | |
50 | |
51 expectDependencyValidationWarning(String warning) { | |
52 expect(schedulePackageValidation(dependency), | |
53 completion(pairOf(isEmpty, someElement(contains(warning))))); | |
54 } | |
55 | |
56 Validator compiledDartdoc(Entrypoint entrypoint) => | |
57 new CompiledDartdocValidator(entrypoint); | |
58 | |
59 Validator dependency(Entrypoint entrypoint) => | |
60 new DependencyValidator(entrypoint); | |
61 | |
62 Validator directory(Entrypoint entrypoint) => | |
63 new DirectoryValidator(entrypoint); | |
64 | |
65 Validator lib(Entrypoint entrypoint) => new LibValidator(entrypoint); | |
66 | |
67 Validator license(Entrypoint entrypoint) => new LicenseValidator(entrypoint); | |
68 | |
69 Validator name(Entrypoint entrypoint) => new NameValidator(entrypoint); | |
70 | |
71 Validator pubspecField(Entrypoint entrypoint) => | |
72 new PubspecFieldValidator(entrypoint); | |
73 | |
74 Function size(int size) { | |
75 return (entrypoint) => | |
76 new SizeValidator(entrypoint, new Future.immediate(size)); | |
77 } | |
78 | |
79 Validator utf8Readme(Entrypoint entrypoint) => | |
80 new Utf8ReadmeValidator(entrypoint); | |
81 | |
82 void scheduleNormalPackage() { | |
83 d.validPackage.create(); | |
84 } | |
85 | |
86 /// Sets up a test package with dependency [dep] and mocks a server with | |
87 /// [hostedVersions] of the package available. | |
88 setUpDependency(Map dep, {List<String> hostedVersions}) { | |
89 useMockClient(new MockClient((request) { | |
90 expect(request.method, equals("GET")); | |
91 expect(request.url.path, equals("/packages/foo.json")); | |
92 | |
93 if (hostedVersions == null) { | |
94 return new Future.immediate(new http.Response("not found", 404)); | |
95 } else { | |
96 return new Future.immediate(new http.Response(json.stringify({ | |
97 "name": "foo", | |
98 "uploaders": ["nweiz@google.com"], | |
99 "versions": hostedVersions | |
100 }), 200)); | |
101 } | |
102 })); | |
103 | |
104 d.dir(appPath, [ | |
105 d.libPubspec("test_pkg", "1.0.0", deps: [dep]) | |
106 ]).create(); | |
107 } | |
108 | |
109 main() { | |
110 initConfig(); | |
111 group('should consider a package valid if it', () { | |
112 setUp(scheduleNormalPackage); | |
113 | |
114 integration('looks normal', () { | |
115 d.dir(appPath, [d.libPubspec("test_pkg", "1.0.0")]).create(); | |
116 expectNoValidationError(dependency); | |
117 expectNoValidationError(lib); | |
118 expectNoValidationError(license); | |
119 expectNoValidationError(name); | |
120 expectNoValidationError(pubspecField); | |
121 }); | |
122 | |
123 integration('has a COPYING file', () { | |
124 schedule(() => deleteEntry(path.join(sandboxDir, appPath, 'LICENSE'))); | |
125 d.file(path.join(appPath, 'COPYING'), '').create(); | |
126 expectNoValidationError(license); | |
127 }); | |
128 | |
129 integration('has a prefixed LICENSE file', () { | |
130 schedule(() => deleteEntry(path.join(sandboxDir, appPath, 'LICENSE'))); | |
131 d.file(path.join(appPath, 'MIT_LICENSE'), '').create(); | |
132 expectNoValidationError(license); | |
133 }); | |
134 | |
135 integration('has a suffixed LICENSE file', () { | |
136 schedule(() => deleteEntry(path.join(sandboxDir, appPath, 'LICENSE'))); | |
137 d.file(path.join(appPath, 'LICENSE.md'), '').create(); | |
138 expectNoValidationError(license); | |
139 }); | |
140 | |
141 integration('has "authors" instead of "author"', () { | |
142 var pkg = packageMap("test_pkg", "1.0.0"); | |
143 pkg["authors"] = [pkg.remove("author")]; | |
144 d.dir(appPath, [d.pubspec(pkg)]).create(); | |
145 expectNoValidationError(pubspecField); | |
146 }); | |
147 | |
148 integration('has a badly-named library in lib/src', () { | |
149 d.dir(appPath, [ | |
150 d.libPubspec("test_pkg", "1.0.0"), | |
151 d.dir("lib", [ | |
152 d.file("test_pkg.dart", "int i = 1;"), | |
153 d.dir("src", [d.file("8ball.dart", "int j = 2;")]) | |
154 ]) | |
155 ]).create(); | |
156 expectNoValidationError(name); | |
157 }); | |
158 | |
159 integration('has a non-Dart file in lib', () { | |
160 d.dir(appPath, [ | |
161 d.libPubspec("test_pkg", "1.0.0"), | |
162 d.dir("lib", [ | |
163 d.file("thing.txt", "woo hoo") | |
164 ]) | |
165 ]).create(); | |
166 expectNoValidationError(lib); | |
167 }); | |
168 | |
169 integration('has a nested directory named "tools"', () { | |
170 d.dir(appPath, [ | |
171 d.dir("foo", [d.dir("tools")]) | |
172 ]).create(); | |
173 expectNoValidationError(directory); | |
174 }); | |
175 | |
176 integration('is <= 10 MB', () { | |
177 expectNoValidationError(size(100)); | |
178 expectNoValidationError(size(10 * math.pow(2, 20))); | |
179 }); | |
180 | |
181 integration('has most but not all files from compiling dartdoc', () { | |
182 d.dir(appPath, [ | |
183 d.dir("doc-out", [ | |
184 d.file("nav.json", ""), | |
185 d.file("index.html", ""), | |
186 d.file("styles.css", ""), | |
187 d.file("dart-logo-small.png", "") | |
188 ]) | |
189 ]).create(); | |
190 expectNoValidationError(compiledDartdoc); | |
191 }); | |
192 | |
193 integration('has a non-primary readme with invalid utf-8', () { | |
194 d.dir(appPath, [ | |
195 d.file("README", "Valid utf-8"), | |
196 d.binaryFile("README.invalid", [192]) | |
197 ]).create(); | |
198 expectNoValidationError(utf8Readme); | |
199 }); | |
200 }); | |
201 | |
202 group('should consider a package invalid if it', () { | |
203 setUp(scheduleNormalPackage); | |
204 | |
205 integration('is missing the "homepage" field', () { | |
206 var pkg = packageMap("test_pkg", "1.0.0"); | |
207 pkg.remove("homepage"); | |
208 d.dir(appPath, [d.pubspec(pkg)]).create(); | |
209 | |
210 expectValidationError(pubspecField); | |
211 }); | |
212 | |
213 integration('is missing the "description" field', () { | |
214 var pkg = packageMap("test_pkg", "1.0.0"); | |
215 pkg.remove("description"); | |
216 d.dir(appPath, [d.pubspec(pkg)]).create(); | |
217 | |
218 expectValidationError(pubspecField); | |
219 }); | |
220 | |
221 integration('is missing the "author" field', () { | |
222 var pkg = packageMap("test_pkg", "1.0.0"); | |
223 pkg.remove("author"); | |
224 d.dir(appPath, [d.pubspec(pkg)]).create(); | |
225 | |
226 expectValidationError(pubspecField); | |
227 }); | |
228 | |
229 integration('has a single author without an email', () { | |
230 var pkg = packageMap("test_pkg", "1.0.0"); | |
231 pkg["author"] = "Nathan Weizenbaum"; | |
232 d.dir(appPath, [d.pubspec(pkg)]).create(); | |
233 | |
234 expectValidationWarning(pubspecField); | |
235 }); | |
236 | |
237 integration('has one of several authors without an email', () { | |
238 var pkg = packageMap("test_pkg", "1.0.0"); | |
239 pkg.remove("author"); | |
240 pkg["authors"] = [ | |
241 "Bob Nystrom <rnystrom@google.com>", | |
242 "Nathan Weizenbaum", | |
243 "John Messerly <jmesserly@google.com>" | |
244 ]; | |
245 d.dir(appPath, [d.pubspec(pkg)]).create(); | |
246 | |
247 expectValidationWarning(pubspecField); | |
248 }); | |
249 | |
250 integration('has a single author without a name', () { | |
251 var pkg = packageMap("test_pkg", "1.0.0"); | |
252 pkg["author"] = "<nweiz@google.com>"; | |
253 d.dir(appPath, [d.pubspec(pkg)]).create(); | |
254 | |
255 expectValidationWarning(pubspecField); | |
256 }); | |
257 | |
258 integration('has one of several authors without a name', () { | |
259 var pkg = packageMap("test_pkg", "1.0.0"); | |
260 pkg.remove("author"); | |
261 pkg["authors"] = [ | |
262 "Bob Nystrom <rnystrom@google.com>", | |
263 "<nweiz@google.com>", | |
264 "John Messerly <jmesserly@google.com>" | |
265 ]; | |
266 d.dir(appPath, [d.pubspec(pkg)]).create(); | |
267 | |
268 expectValidationWarning(pubspecField); | |
269 }); | |
270 | |
271 integration('has no LICENSE file', () { | |
272 schedule(() => deleteEntry(path.join(sandboxDir, appPath, 'LICENSE'))); | |
273 expectValidationError(license); | |
274 }); | |
275 | |
276 integration('has an empty package name', () { | |
277 d.dir(appPath, [d.libPubspec("", "1.0.0")]).create(); | |
278 expectValidationError(name); | |
279 }); | |
280 | |
281 integration('has a package name with an invalid character', () { | |
282 d.dir(appPath, [d.libPubspec("test-pkg", "1.0.0")]).create(); | |
283 expectValidationError(name); | |
284 }); | |
285 | |
286 integration('has a package name that begins with a number', () { | |
287 d.dir(appPath, [d.libPubspec("8ball", "1.0.0")]).create(); | |
288 expectValidationError(name); | |
289 }); | |
290 | |
291 integration('has a package name that contains upper-case letters', () { | |
292 d.dir(appPath, [d.libPubspec("TestPkg", "1.0.0")]).create(); | |
293 expectValidationWarning(name); | |
294 }); | |
295 | |
296 integration('has a package name that is a Dart reserved word', () { | |
297 d.dir(appPath, [d.libPubspec("final", "1.0.0")]).create(); | |
298 expectValidationError(name); | |
299 }); | |
300 | |
301 integration('has a library name with an invalid character', () { | |
302 d.dir(appPath, [ | |
303 d.libPubspec("test_pkg", "1.0.0"), | |
304 d.dir("lib", [d.file("test-pkg.dart", "int i = 0;")]) | |
305 ]).create(); | |
306 expectValidationWarning(name); | |
307 }); | |
308 | |
309 integration('has a library name that begins with a number', () { | |
310 d.dir(appPath, [ | |
311 d.libPubspec("test_pkg", "1.0.0"), | |
312 d.dir("lib", [d.file("8ball.dart", "int i = 0;")]) | |
313 ]).create(); | |
314 expectValidationWarning(name); | |
315 }); | |
316 | |
317 integration('has a library name that contains upper-case letters', () { | |
318 d.dir(appPath, [ | |
319 d.libPubspec("test_pkg", "1.0.0"), | |
320 d.dir("lib", [d.file("TestPkg.dart", "int i = 0;")]) | |
321 ]).create(); | |
322 expectValidationWarning(name); | |
323 }); | |
324 | |
325 integration('has a library name that is a Dart reserved word', () { | |
326 d.dir(appPath, [ | |
327 d.libPubspec("test_pkg", "1.0.0"), | |
328 d.dir("lib", [d.file("for.dart", "int i = 0;")]) | |
329 ]).create(); | |
330 expectValidationWarning(name); | |
331 }); | |
332 | |
333 integration('has a single library named differently than the package', () { | |
334 schedule(() => | |
335 deleteEntry(path.join(sandboxDir, appPath, "lib", "test_pkg.dart"))); | |
336 d.dir(appPath, [ | |
337 d.dir("lib", [d.file("best_pkg.dart", "int i = 0;")]) | |
338 ]).create(); | |
339 expectValidationWarning(name); | |
340 }); | |
341 | |
342 integration('has no lib directory', () { | |
343 schedule(() => deleteEntry(path.join(sandboxDir, appPath, "lib"))); | |
344 expectValidationError(lib); | |
345 }); | |
346 | |
347 integration('has an empty lib directory', () { | |
348 schedule(() => | |
349 deleteEntry(path.join(sandboxDir, appPath, "lib", "test_pkg.dart"))); | |
350 expectValidationError(lib); | |
351 }); | |
352 | |
353 integration('has a lib directory containing only src', () { | |
354 schedule(() => | |
355 deleteEntry(path.join(sandboxDir, appPath, "lib", "test_pkg.dart"))); | |
356 d.dir(appPath, [ | |
357 d.dir("lib", [ | |
358 d.dir("src", [d.file("test_pkg.dart", "int i = 0;")]) | |
359 ]) | |
360 ]).create(); | |
361 expectValidationError(lib); | |
362 }); | |
363 | |
364 group('has a git dependency', () { | |
365 group('where a hosted version exists', () { | |
366 integration("and should suggest the hosted primary version", () { | |
367 setUpDependency({'git': 'git://github.com/dart-lang/foo'}, | |
368 hostedVersions: ["3.0.0-pre", "2.0.0", "1.0.0"]); | |
369 expectDependencyValidationWarning(' foo: ">=2.0.0 <3.0.0"'); | |
370 }); | |
371 | |
372 integration("and should suggest the hosted prerelease version if " | |
373 "it's the only version available", () { | |
374 setUpDependency({'git': 'git://github.com/dart-lang/foo'}, | |
375 hostedVersions: ["3.0.0-pre", "2.0.0-pre"]); | |
376 expectDependencyValidationWarning(' foo: ">=3.0.0-pre <4.0.0"'); | |
377 }); | |
378 | |
379 integration("and should suggest a tighter constraint if primary is " | |
380 "pre-1.0.0", () { | |
381 setUpDependency({'git': 'git://github.com/dart-lang/foo'}, | |
382 hostedVersions: ["0.0.1", "0.0.2"]); | |
383 expectDependencyValidationWarning(' foo: ">=0.0.2 <0.0.3"'); | |
384 }); | |
385 }); | |
386 | |
387 group('where no hosted version exists', () { | |
388 integration("and should use the other source's version", () { | |
389 setUpDependency({ | |
390 'git': 'git://github.com/dart-lang/foo', | |
391 'version': '>=1.0.0 <2.0.0' | |
392 }); | |
393 expectDependencyValidationWarning(' foo: ">=1.0.0 <2.0.0"'); | |
394 }); | |
395 | |
396 integration("and should use the other source's unquoted version if " | |
397 "concrete", () { | |
398 setUpDependency({ | |
399 'git': 'git://github.com/dart-lang/foo', | |
400 'version': '0.2.3' | |
401 }); | |
402 expectDependencyValidationWarning(' foo: 0.2.3'); | |
403 }); | |
404 }); | |
405 }); | |
406 | |
407 group('has a path dependency', () { | |
408 group('where a hosted version exists', () { | |
409 integration("and should suggest the hosted primary version", () { | |
410 setUpDependency({'path': path.join(sandboxDir, 'foo')}, | |
411 hostedVersions: ["3.0.0-pre", "2.0.0", "1.0.0"]); | |
412 expectDependencyValidationError(' foo: ">=2.0.0 <3.0.0"'); | |
413 }); | |
414 | |
415 integration("and should suggest the hosted prerelease version if " | |
416 "it's the only version available", () { | |
417 setUpDependency({'path': path.join(sandboxDir, 'foo')}, | |
418 hostedVersions: ["3.0.0-pre", "2.0.0-pre"]); | |
419 expectDependencyValidationError(' foo: ">=3.0.0-pre <4.0.0"'); | |
420 }); | |
421 | |
422 integration("and should suggest a tighter constraint if primary is " | |
423 "pre-1.0.0", () { | |
424 setUpDependency({'path': path.join(sandboxDir, 'foo')}, | |
425 hostedVersions: ["0.0.1", "0.0.2"]); | |
426 expectDependencyValidationError(' foo: ">=0.0.2 <0.0.3"'); | |
427 }); | |
428 }); | |
429 | |
430 group('where no hosted version exists', () { | |
431 integration("and should use the other source's version", () { | |
432 setUpDependency({ | |
433 'path': path.join(sandboxDir, 'foo'), | |
434 'version': '>=1.0.0 <2.0.0' | |
435 }); | |
436 expectDependencyValidationError(' foo: ">=1.0.0 <2.0.0"'); | |
437 }); | |
438 | |
439 integration("and should use the other source's unquoted version if " | |
440 "concrete", () { | |
441 setUpDependency({ | |
442 'path': path.join(sandboxDir, 'foo'), | |
443 'version': '0.2.3' | |
444 }); | |
445 expectDependencyValidationError(' foo: 0.2.3'); | |
446 }); | |
447 }); | |
448 }); | |
449 | |
450 group('has an unconstrained dependency', () { | |
451 group('and it should not suggest a version', () { | |
452 integration("if there's no lockfile", () { | |
453 d.dir(appPath, [ | |
454 d.libPubspec("test_pkg", "1.0.0", deps: [ | |
455 {'hosted': 'foo'} | |
456 ]) | |
457 ]).create(); | |
458 | |
459 expect(schedulePackageValidation(dependency), completion( | |
460 pairOf(isEmpty, everyElement(isNot(contains("\n foo:")))))); | |
461 }); | |
462 | |
463 integration("if the lockfile doesn't have an entry for the " | |
464 "dependency", () { | |
465 d.dir(appPath, [ | |
466 d.libPubspec("test_pkg", "1.0.0", deps: [ | |
467 {'hosted': 'foo'} | |
468 ]), | |
469 d.file("pubspec.lock", json.stringify({ | |
470 'packages': { | |
471 'bar': { | |
472 'version': '1.2.3', | |
473 'source': 'hosted', | |
474 'description': { | |
475 'name': 'bar', | |
476 'url': 'http://pub.dartlang.org' | |
477 } | |
478 } | |
479 } | |
480 })) | |
481 ]).create(); | |
482 | |
483 expect(schedulePackageValidation(dependency), completion( | |
484 pairOf(isEmpty, everyElement(isNot(contains("\n foo:")))))); | |
485 }); | |
486 }); | |
487 | |
488 group('with a lockfile', () { | |
489 integration('and it should suggest a constraint based on the locked ' | |
490 'version', () { | |
491 d.dir(appPath, [ | |
492 d.libPubspec("test_pkg", "1.0.0", deps: [ | |
493 {'hosted': 'foo'} | |
494 ]), | |
495 d.file("pubspec.lock", json.stringify({ | |
496 'packages': { | |
497 'foo': { | |
498 'version': '1.2.3', | |
499 'source': 'hosted', | |
500 'description': { | |
501 'name': 'foo', | |
502 'url': 'http://pub.dartlang.org' | |
503 } | |
504 } | |
505 } | |
506 })) | |
507 ]).create(); | |
508 | |
509 expectDependencyValidationWarning(' foo: ">=1.2.3 <2.0.0"'); | |
510 }); | |
511 | |
512 integration('and it should suggest a concrete constraint if the locked ' | |
513 'version is pre-1.0.0', () { | |
514 d.dir(appPath, [ | |
515 d.libPubspec("test_pkg", "1.0.0", deps: [ | |
516 {'hosted': 'foo'} | |
517 ]), | |
518 d.file("pubspec.lock", json.stringify({ | |
519 'packages': { | |
520 'foo': { | |
521 'version': '0.1.2', | |
522 'source': 'hosted', | |
523 'description': { | |
524 'name': 'foo', | |
525 'url': 'http://pub.dartlang.org' | |
526 } | |
527 } | |
528 } | |
529 })) | |
530 ]).create(); | |
531 | |
532 expectDependencyValidationWarning(' foo: ">=0.1.2 <0.1.3"'); | |
533 }); | |
534 }); | |
535 }); | |
536 | |
537 integration('has a hosted dependency on itself', () { | |
538 d.dir(appPath, [ | |
539 d.libPubspec("test_pkg", "1.0.0", deps: [ | |
540 {'hosted': {'name': 'test_pkg', 'version': '>=1.0.0'}} | |
541 ]) | |
542 ]).create(); | |
543 | |
544 expectValidationWarning(dependency); | |
545 }); | |
546 | |
547 group('has a top-level directory named', () { | |
548 setUp(scheduleNormalPackage); | |
549 | |
550 var names = ["tools", "tests", "docs", "examples", "sample", "samples"]; | |
551 for (var name in names) { | |
552 integration('"$name"', () { | |
553 d.dir(appPath, [d.dir(name)]).create(); | |
554 expectValidationWarning(directory); | |
555 }); | |
556 } | |
557 }); | |
558 | |
559 integration('is more than 10 MB', () { | |
560 expectValidationError(size(10 * math.pow(2, 20) + 1)); | |
561 }); | |
562 | |
563 integration('contains compiled dartdoc', () { | |
564 d.dir(appPath, [ | |
565 d.dir('doc-out', [ | |
566 d.file('nav.json', ''), | |
567 d.file('index.html', ''), | |
568 d.file('styles.css', ''), | |
569 d.file('dart-logo-small.png', ''), | |
570 d.file('client-live-nav.js', '') | |
571 ]) | |
572 ]).create(); | |
573 | |
574 expectValidationWarning(compiledDartdoc); | |
575 }); | |
576 | |
577 integration('has a README with invalid utf-8', () { | |
578 d.dir(appPath, [ | |
579 d.binaryFile("README", [192]) | |
580 ]).create(); | |
581 expectValidationWarning(utf8Readme); | |
582 }); | |
583 }); | |
584 } | |
OLD | NEW |