pkgtools/pkglint: update to 19.3.9 Changes since 19.3.8: Match man pages in ALTERNATIVES with their counterparts in PLIST. In PLIST files, ${PKGMANDIR} may be abbreviated as a simple "man", but not in ALTERNATIVES.diff -r1.608 -r1.609 pkgsrc/pkgtools/pkglint/Makefile
(rillig)
@@ -1,16 +1,16 @@ | @@ -1,16 +1,16 @@ | |||
1 | # $NetBSD: Makefile,v 1.608 2019/11/17 02:06:01 rillig Exp $ | 1 | # $NetBSD: Makefile,v 1.609 2019/11/19 06:51:38 rillig Exp $ | |
2 | 2 | |||
3 | PKGNAME= pkglint-19.3.8 | 3 | PKGNAME= pkglint-19.3.9 | |
4 | CATEGORIES= pkgtools | 4 | CATEGORIES= pkgtools | |
5 | DISTNAME= tools | 5 | DISTNAME= tools | |
6 | MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} | 6 | MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} | |
7 | GITHUB_PROJECT= tools | 7 | GITHUB_PROJECT= tools | |
8 | GITHUB_TAG= 92d8274bd7b8a4c65f24bafe401a029e58392704 | 8 | GITHUB_TAG= 92d8274bd7b8a4c65f24bafe401a029e58392704 | |
9 | 9 | |||
10 | MAINTAINER= rillig@NetBSD.org | 10 | MAINTAINER= rillig@NetBSD.org | |
11 | HOMEPAGE= https://github.com/rillig/pkglint | 11 | HOMEPAGE= https://github.com/rillig/pkglint | |
12 | COMMENT= Verifier for NetBSD packages | 12 | COMMENT= Verifier for NetBSD packages | |
13 | LICENSE= 2-clause-bsd | 13 | LICENSE= 2-clause-bsd | |
14 | CONFLICTS+= pkglint4-[0-9]* | 14 | CONFLICTS+= pkglint4-[0-9]* | |
15 | 15 | |||
16 | USE_TOOLS+= pax | 16 | USE_TOOLS+= pax |
@@ -18,26 +18,29 @@ func CheckFileAlternatives(filename stri | @@ -18,26 +18,29 @@ func CheckFileAlternatives(filename stri | |||
18 | 18 | |||
19 | checkPlistWrapper := func(line *Line, wrapper string) { | 19 | checkPlistWrapper := func(line *Line, wrapper string) { | |
20 | if plist.Files[wrapper] != nil { | 20 | if plist.Files[wrapper] != nil { | |
21 | line.Errorf("Alternative wrapper %q must not appear in the PLIST.", wrapper) | 21 | line.Errorf("Alternative wrapper %q must not appear in the PLIST.", wrapper) | |
22 | } | 22 | } | |
23 | } | 23 | } | |
24 | 24 | |||
25 | checkPlistAlternative := func(line *Line, alternative string) { | 25 | checkPlistAlternative := func(line *Line, alternative string) { | |
26 | relImplementation := strings.Replace(alternative, "@PREFIX@/", "", 1) | 26 | relImplementation := strings.Replace(alternative, "@PREFIX@/", "", 1) | |
27 | plistName := replaceAll(relImplementation, `@(\w+)@`, "${$1}") | 27 | plistName := replaceAll(relImplementation, `@(\w+)@`, "${$1}") | |
28 | if plist.Files[plistName] != nil || G.Pkg.vars.IsDefined("ALTERNATIVES_SRC") { | 28 | if plist.Files[plistName] != nil || G.Pkg.vars.IsDefined("ALTERNATIVES_SRC") { | |
29 | return | 29 | return | |
30 | } | 30 | } | |
31 | if plist.Files[strings.Replace(plistName, "${PKGMANDIR}", "man", 1)] != nil { | |||
32 | return | |||
33 | } | |||
31 | 34 | |||
32 | switch { | 35 | switch { | |
33 | 36 | |||
34 | case hasPrefix(alternative, "/"): | 37 | case hasPrefix(alternative, "/"): | |
35 | // It's possible but unusual to refer to a fixed absolute path. | 38 | // It's possible but unusual to refer to a fixed absolute path. | |
36 | // These cannot be mentioned in the PLIST since they are not part of the package. | 39 | // These cannot be mentioned in the PLIST since they are not part of the package. | |
37 | break | 40 | break | |
38 | 41 | |||
39 | case plistName == alternative: | 42 | case plistName == alternative: | |
40 | line.Errorf("Alternative implementation %q must appear in the PLIST.", alternative) | 43 | line.Errorf("Alternative implementation %q must appear in the PLIST.", alternative) | |
41 | 44 | |||
42 | default: | 45 | default: | |
43 | line.Errorf("Alternative implementation %q must appear in the PLIST as %q.", alternative, plistName) | 46 | line.Errorf("Alternative implementation %q must appear in the PLIST as %q.", alternative, plistName) |
@@ -96,13 +96,35 @@ func (s *Suite) Test_CheckFileAlternativ | @@ -96,13 +96,35 @@ func (s *Suite) Test_CheckFileAlternativ | |||
96 | // really had this case in mind when I initially wrote the code in | 96 | // really had this case in mind when I initially wrote the code in | |
97 | // CheckFileAlternatives. | 97 | // CheckFileAlternatives. | |
98 | t.SetUpPackage("category/package", | 98 | t.SetUpPackage("category/package", | |
99 | "ALTERNATIVES_SRC=\talts") | 99 | "ALTERNATIVES_SRC=\talts") | |
100 | t.CreateFileLines("category/package/ALTERNATIVES", | 100 | t.CreateFileLines("category/package/ALTERNATIVES", | |
101 | "bin/pgm @PREFIX@/bin/gnu-program", | 101 | "bin/pgm @PREFIX@/bin/gnu-program", | |
102 | "bin/pgm @PREFIX@/bin/nb-program") | 102 | "bin/pgm @PREFIX@/bin/nb-program") | |
103 | t.FinishSetUp() | 103 | t.FinishSetUp() | |
104 | 104 | |||
105 | G.Check(t.File("category/package")) | 105 | G.Check(t.File("category/package")) | |
106 | 106 | |||
107 | t.CheckOutputEmpty() | 107 | t.CheckOutputEmpty() | |
108 | } | 108 | } | |
109 | ||||
110 | // When a man page is mentioned in the ALTERNATIVES file, it must use the | |||
111 | // PKGMANDIR variable. In the PLIST files though, there is some magic | |||
112 | // in the pkgsrc infrastructure that maps man/ to ${PKGMANDIR}, which | |||
113 | // leads to a bit less typing. | |||
114 | // | |||
115 | // Seen in graphics/py-blockdiag. | |||
116 | func (s *Suite) Test_CheckFileAlternatives__PLIST_man(c *check.C) { | |||
117 | t := s.Init(c) | |||
118 | ||||
119 | t.SetUpPackage("category/package") | |||
120 | t.CreateFileLines("category/package/ALTERNATIVES", | |||
121 | "@PKGMANDIR@/man1/blockdiag @PREFIX@/@PKGMANDIR@/man1/blockdiag-@PYVERSSUFFIX@.1") | |||
122 | t.CreateFileLines("category/package/PLIST", | |||
123 | PlistCvsID, | |||
124 | "man/man1/blockdiag-${PYVERSSUFFIX}.1") | |||
125 | t.FinishSetUp() | |||
126 | ||||
127 | G.Check(t.File("category/package")) | |||
128 | ||||
129 | t.CheckOutputEmpty() | |||
130 | } |
@@ -100,27 +100,27 @@ func (m MkVarUseModifier) MatchMatch() ( | @@ -100,27 +100,27 @@ func (m MkVarUseModifier) MatchMatch() ( | |||
100 | exact := !strings.ContainsAny(m.Text[1:], "*?[\\$") | 100 | exact := !strings.ContainsAny(m.Text[1:], "*?[\\$") | |
101 | return true, m.Text[0] == 'M', m.Text[1:], exact | 101 | return true, m.Text[0] == 'M', m.Text[1:], exact | |
102 | } | 102 | } | |
103 | return false, false, "", false | 103 | return false, false, "", false | |
104 | } | 104 | } | |
105 | 105 | |||
106 | func (m MkVarUseModifier) IsToLower() bool { return m.Text == "tl" } | 106 | func (m MkVarUseModifier) IsToLower() bool { return m.Text == "tl" } | |
107 | 107 | |||
108 | // ChangesWords returns true if applying this modifier to a list variable | 108 | // ChangesWords returns true if applying this modifier to a list variable | |
109 | // may change the number of words in the list, or their boundaries. | 109 | // may change the number of words in the list, or their boundaries. | |
110 | func (m MkVarUseModifier) ChangesWords() bool { | 110 | func (m MkVarUseModifier) ChangesWords() bool { | |
111 | text := m.Text | 111 | text := m.Text | |
112 | 112 | |||
113 | // See MkParser.VarUseModifiers for the meaning of these modifiers. | 113 | // See MkParser.varUseModifier for the meaning of these modifiers. | |
114 | switch text[0] { | 114 | switch text[0] { | |
115 | 115 | |||
116 | case 'E', 'H', 'M', 'N', 'O', 'R', 'T': | 116 | case 'E', 'H', 'M', 'N', 'O', 'R', 'T': | |
117 | return false | 117 | return false | |
118 | 118 | |||
119 | case 'C', 'Q', 'S': | 119 | case 'C', 'Q', 'S': | |
120 | // For the :C and :S modifiers, a more detailed analysis could reveal | 120 | // For the :C and :S modifiers, a more detailed analysis could reveal | |
121 | // cases that don't change the structure, such as :S,a,b,g or | 121 | // cases that don't change the structure, such as :S,a,b,g or | |
122 | // :C,[0-9A-Za-z_],.,g, but not :C,x,,g. | 122 | // :C,[0-9A-Za-z_],.,g, but not :C,x,,g. | |
123 | return true | 123 | return true | |
124 | } | 124 | } | |
125 | 125 | |||
126 | switch text { | 126 | switch text { |
@@ -209,27 +209,27 @@ func (src *Pkgsrc) loadDocChangesFromFil | @@ -209,27 +209,27 @@ func (src *Pkgsrc) loadDocChangesFromFil | |||
209 | } | 209 | } | |
210 | 210 | |||
211 | change := src.parseDocChange(line, warn) | 211 | change := src.parseDocChange(line, warn) | |
212 | if change == nil { | 212 | if change == nil { | |
213 | continue | 213 | continue | |
214 | } | 214 | } | |
215 | 215 | |||
216 | changes = append(changes, change) | 216 | changes = append(changes, change) | |
217 | 217 | |||
218 | if !warn { | 218 | if !warn { | |
219 | continue | 219 | continue | |
220 | } | 220 | } | |
221 | 221 | |||
222 | if year != "" && len(change.Date) >= 4 && change.Date[0:4] != year { | 222 | if year != "" && change.Date[0:4] != year { | |
223 | line.Warnf("Year %q for %s does not match the filename %s.", | 223 | line.Warnf("Year %q for %s does not match the filename %s.", | |
224 | change.Date[0:4], change.Pkgpath, filename) | 224 | change.Date[0:4], change.Pkgpath, filename) | |
225 | } | 225 | } | |
226 | 226 | |||
227 | if len(changes) >= 2 && year != "" { | 227 | if len(changes) >= 2 && year != "" { | |
228 | if prev := changes[len(changes)-2]; change.Date < prev.Date { | 228 | if prev := changes[len(changes)-2]; change.Date < prev.Date { | |
229 | line.Warnf("Date %q for %s is earlier than %q in %s.", | 229 | line.Warnf("Date %q for %s is earlier than %q in %s.", | |
230 | change.Date, change.Pkgpath, prev.Date, line.RefToLocation(prev.Location)) | 230 | change.Date, change.Pkgpath, prev.Date, line.RefToLocation(prev.Location)) | |
231 | line.Explain( | 231 | line.Explain( | |
232 | "The entries in doc/CHANGES should be in chronological order, and", | 232 | "The entries in doc/CHANGES should be in chronological order, and", | |
233 | "all dates are assumed to be in the UTC timezone, to prevent time", | 233 | "all dates are assumed to be in the UTC timezone, to prevent time", | |
234 | "warps.", | 234 | "warps.", | |
235 | "", | 235 | "", |
@@ -358,96 +358,100 @@ func (s *Suite) Test_Pkgsrc_parseDocChan | @@ -358,96 +358,100 @@ func (s *Suite) Test_Pkgsrc_parseDocChan | |||
358 | test("\tAdded something [author date]", | 358 | test("\tAdded something [author date]", | |
359 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | 359 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | |
360 | "\tAdded something [author date]") | 360 | "\tAdded something [author date]") | |
361 | 361 | |||
362 | test("\tAdded category/package 1.0 [author 2019-11-17]", | 362 | test("\tAdded category/package 1.0 [author 2019-11-17]", | |
363 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | 363 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | |
364 | "\tAdded category/package 1.0 [author 2019-11-17]") | 364 | "\tAdded category/package 1.0 [author 2019-11-17]") | |
365 | 365 | |||
366 | test("\t\tToo large indentation", | 366 | test("\t\tToo large indentation", | |
367 | "WARN: doc/CHANGES-2019:123: Package changes should be indented using a single tab, not \"\\t\\t\".") | 367 | "WARN: doc/CHANGES-2019:123: Package changes should be indented using a single tab, not \"\\t\\t\".") | |
368 | test("\t Too large indentation", | 368 | test("\t Too large indentation", | |
369 | "WARN: doc/CHANGES-2019:123: Package changes should be indented using a single tab, not \"\\t \".") | 369 | "WARN: doc/CHANGES-2019:123: Package changes should be indented using a single tab, not \"\\t \".") | |
370 | 370 | |||
371 | test("\t", | |||
372 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t") | |||
373 | test("\t1", | |||
374 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1") | |||
371 | test("\t1 2 3 4", | 375 | test("\t1 2 3 4", | |
372 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4") | 376 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4") | |
373 | test("\t1 2 3 4 5", | 377 | test("\t1 2 3 4 5", | |
374 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5") | 378 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5") | |
375 | test("\t1 2 3 4 5 6", | 379 | test("\t1 2 3 4 5 6", | |
376 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5 6") | 380 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5 6") | |
377 | test("\t1 2 3 4 5 6 7", | 381 | test("\t1 2 3 4 5 6 7", | |
378 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5 6 7") | 382 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5 6 7") | |
379 | test("\t1 2 [3 4", | 383 | test("\t1 2 [3 4", | |
380 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 [3 4") | 384 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 [3 4") | |
381 | test("\t1 2 [3 4]", | 385 | test("\t1 2 [3 4]", | |
382 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 [3 4]") | 386 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 [3 4]") | |
383 | test("\tAdded 2 [3 4]", | 387 | test("\tAdded 2 [3 4]", | |
384 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \tAdded 2 [3 4]") | 388 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \tAdded 2 [3 4]") | |
385 | 389 | |||
386 | test("\tAdded pkgpath version 1.0 [author 2019-01-01]", | 390 | test("\tAdded pkgpath version 1.0 [author 2019-01-01]", | |
387 | nil...) | 391 | nil...) | |
388 | 392 | |||
389 | // "to" is wrong | 393 | // "to" is wrong | |
390 | test("\tAdded pkgpath to 1.0 [author date]", | 394 | test("\tAdded pkgpath to 1.0 [author 2019-01-01]", | |
391 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | 395 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | |
392 | "\tAdded pkgpath to 1.0 [author date]") | 396 | "\tAdded pkgpath to 1.0 [author 2019-01-01]") | |
393 | 397 | |||
394 | test("\tUpdated pkgpath to 1.0 [author 2019-01-01]", | 398 | test("\tUpdated pkgpath to 1.0 [author 2019-01-01]", | |
395 | nil...) | 399 | nil...) | |
396 | 400 | |||
397 | // "from" is wrong | 401 | // "from" is wrong | |
398 | test("\tUpdated pkgpath from 1.0 [author date]", | 402 | test("\tUpdated pkgpath from 1.0 [author 2019-01-01]", | |
399 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | 403 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | |
400 | "\tUpdated pkgpath from 1.0 [author date]") | 404 | "\tUpdated pkgpath from 1.0 [author 2019-01-01]") | |
401 | 405 | |||
402 | test("\tDowngraded pkgpath to 1.0 [author 2019-01-01]", | 406 | test("\tDowngraded pkgpath to 1.0 [author 2019-01-01]", | |
403 | nil...) | 407 | nil...) | |
404 | 408 | |||
405 | // "from" is wrong | 409 | // "from" is wrong | |
406 | test("\tDowngraded pkgpath from 1.0 [author date]", | 410 | test("\tDowngraded pkgpath from 1.0 [author 2019-01-01]", | |
407 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | 411 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | |
408 | "\tDowngraded pkgpath from 1.0 [author date]") | 412 | "\tDowngraded pkgpath from 1.0 [author 2019-01-01]") | |
409 | 413 | |||
410 | test("\tRemoved pkgpath [author 2019-01-01]", | 414 | test("\tRemoved pkgpath [author 2019-01-01]", | |
411 | nil...) | 415 | nil...) | |
412 | 416 | |||
413 | test("\tRemoved pkgpath successor pkgpath [author 2019-01-01]", | 417 | test("\tRemoved pkgpath successor pkgpath [author 2019-01-01]", | |
414 | nil...) | 418 | nil...) | |
415 | 419 | |||
416 | // "and" is wrong | 420 | // "and" is wrong | |
417 | test("\tRemoved pkgpath and pkgpath [author date]", | 421 | test("\tRemoved pkgpath and pkgpath [author 2019-01-01]", | |
418 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | 422 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | |
419 | "\tRemoved pkgpath and pkgpath [author date]") | 423 | "\tRemoved pkgpath and pkgpath [author 2019-01-01]") | |
420 | 424 | |||
421 | test("\tRenamed pkgpath to other [author 2019-01-01]", | 425 | test("\tRenamed pkgpath to other [author 2019-01-01]", | |
422 | nil...) | 426 | nil...) | |
423 | 427 | |||
424 | // "from" is wrong | 428 | // "from" is wrong | |
425 | test("\tRenamed pkgpath from previous [author date]", | 429 | test("\tRenamed pkgpath from previous [author 2019-01-01]", | |
426 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | 430 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | |
427 | "\tRenamed pkgpath from previous [author date]") | 431 | "\tRenamed pkgpath from previous [author 2019-01-01]") | |
428 | 432 | |||
429 | test("\tMoved pkgpath to other [author 2019-01-01]", | 433 | test("\tMoved pkgpath to other [author 2019-01-01]", | |
430 | nil...) | 434 | nil...) | |
431 | 435 | |||
432 | // "from" is wrong | 436 | // "from" is wrong | |
433 | test("\tMoved pkgpath from previous [author date]", | 437 | test("\tMoved pkgpath from previous [author 2019-01-01]", | |
434 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | 438 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | |
435 | "\tMoved pkgpath from previous [author date]") | 439 | "\tMoved pkgpath from previous [author 2019-01-01]") | |
436 | 440 | |||
437 | // "Split" is wrong | 441 | // "Split" is wrong | |
438 | test("\tSplit pkgpath into a and b [author date]", | 442 | test("\tSplit pkgpath into a and b [author 2019-01-01]", | |
439 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | 443 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | |
440 | "\tSplit pkgpath into a and b [author date]") | 444 | "\tSplit pkgpath into a and b [author 2019-01-01]") | |
441 | 445 | |||
442 | // Entries ending in a colon are used for infrastructure changes. | 446 | // Entries ending in a colon are used for infrastructure changes. | |
443 | test("\tmk: remove support for USE_CROSSBASE [author 2016-06-19]", | 447 | test("\tmk: remove support for USE_CROSSBASE [author 2016-06-19]", | |
444 | nil...) | 448 | nil...) | |
445 | 449 | |||
446 | test("\tAdded category/pkgpath version 1.0 [author-dash 2019-01-01]", | 450 | test("\tAdded category/pkgpath version 1.0 [author-dash 2019-01-01]", | |
447 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | 451 | "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+ | |
448 | "\tAdded category/pkgpath version 1.0 [author-dash 2019-01-01]") | 452 | "\tAdded category/pkgpath version 1.0 [author-dash 2019-01-01]") | |
449 | } | 453 | } | |
450 | 454 | |||
451 | func (s *Suite) Test_Pkgsrc_checkRemovedAfterLastFreeze(c *check.C) { | 455 | func (s *Suite) Test_Pkgsrc_checkRemovedAfterLastFreeze(c *check.C) { | |
452 | t := s.Init(c) | 456 | t := s.Init(c) | |
453 | 457 |
@@ -1,16 +1,16 @@ | @@ -1,16 +1,16 @@ | |||
1 | package pkglint | 1 | package pkglint | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | "gopkg.in/check.v1" | 4 | "gopkg.in/check.v1" | |
5 | "netbsd.org/pkglint/intqa" | 5 | "netbsd.org/pkglint/intqa" | |
6 | ) | 6 | ) | |
7 | 7 | |||
8 | // Ensures that all test names follow a common naming scheme: | 8 | // Ensures that all test names follow a common naming scheme: | |
9 | // | 9 | // | |
10 | // Test_${Type}_${Method}__${description_using_underscores} | 10 | // Test_${Type}_${Method}__${description_using_underscores} | |
11 | func (s *Suite) Test__test_names(c *check.C) { | 11 | func (s *Suite) Test__test_names(c *check.C) { | |
12 | ck := intqa.NewTestNameChecker(c.Errorf) | 12 | ck := intqa.NewTestNameChecker(c.Errorf) | |
13 | ck.IgnoreFiles("*yacc.go") | 13 | ck.Configure("*", "*", "*", -intqa.EMissingTest) | |
14 | ck.Enable(intqa.EAll, -intqa.EMissingTest) | 14 | ck.Configure("*yacc.go", "*", "*", intqa.ENone) | |
15 | ck.Check() | 15 | ck.Check() | |
16 | } | 16 | } |
@@ -401,16 +401,16 @@ func (s *Suite) Test_Options_Help__with_ | @@ -401,16 +401,16 @@ func (s *Suite) Test_Options_Help__with_ | |||
401 | " -W, --warnings=warning,... Print selected warnings\n"+ | 401 | " -W, --warnings=warning,... Print selected warnings\n"+ | |
402 | "\n"+ | 402 | "\n"+ | |
403 | " Flags for -W, --warnings:\n"+ | 403 | " Flags for -W, --warnings:\n"+ | |
404 | " all all of the following\n"+ | 404 | " all all of the following\n"+ | |
405 | " none none of the following\n"+ | 405 | " none none of the following\n"+ | |
406 | " basic Print basic warnings (enabled)\n"+ | 406 | " basic Print basic warnings (enabled)\n"+ | |
407 | " extra Print extra warnings (disabled)\n"+ | 407 | " extra Print extra warnings (disabled)\n"+ | |
408 | "\n"+ | 408 | "\n"+ | |
409 | " (Prefix a flag with \"no-\" to disable it.)\n") | 409 | " (Prefix a flag with \"no-\" to disable it.)\n") | |
410 | } | 410 | } | |
411 | 411 | |||
412 | func (s *Suite) Test__test_names(c *check.C) { | 412 | func (s *Suite) Test__test_names(c *check.C) { | |
413 | ck := intqa.NewTestNameChecker(c.Errorf) | 413 | ck := intqa.NewTestNameChecker(c.Errorf) | |
414 | ck.Enable(intqa.EAll, -intqa.EMissingTest) | 414 | ck.Configure("*", "*", "*", -intqa.EMissingTest) | |
415 | ck.Check() | 415 | ck.Check() | |
416 | } | 416 | } |
@@ -20,16 +20,16 @@ func (s *Suite) Test_Histogram(c *check. | @@ -20,16 +20,16 @@ func (s *Suite) Test_Histogram(c *check. | |||
20 | hgr.Add("two", 2) | 20 | hgr.Add("two", 2) | |
21 | hgr.Add("three", 3) | 21 | hgr.Add("three", 3) | |
22 | 22 | |||
23 | var out strings.Builder | 23 | var out strings.Builder | |
24 | hgr.PrintStats(&out, "caption", 2) | 24 | hgr.PrintStats(&out, "caption", 2) | |
25 | 25 | |||
26 | c.Check(out.String(), check.Equals, ""+ | 26 | c.Check(out.String(), check.Equals, ""+ | |
27 | "caption 3 three\n"+ | 27 | "caption 3 three\n"+ | |
28 | "caption 2 two\n") | 28 | "caption 2 two\n") | |
29 | } | 29 | } | |
30 | 30 | |||
31 | func (s *Suite) Test__test_names(c *check.C) { | 31 | func (s *Suite) Test__test_names(c *check.C) { | |
32 | ck := intqa.NewTestNameChecker(c.Errorf) | 32 | ck := intqa.NewTestNameChecker(c.Errorf) | |
33 | ck.Enable(intqa.EAll, -intqa.EMissingTest) | 33 | ck.Configure("*", "*", "*", -intqa.EMissingTest) | |
34 | ck.Check() | 34 | ck.Check() | |
35 | } | 35 | } |
@@ -1,33 +1,34 @@ | @@ -1,33 +1,34 @@ | |||
1 | // Package intqa provides quality assurance for the pkglint code. | 1 | // Package intqa provides quality assurance for the pkglint code. | |
2 | package intqa | 2 | package intqa | |
3 | 3 | |||
4 | import ( | 4 | import ( | |
5 | "fmt" | 5 | "fmt" | |
6 | "go/ast" | 6 | "go/ast" | |
7 | "go/parser" | 7 | "go/parser" | |
8 | "go/token" | 8 | "go/token" | |
9 | "io" | 9 | "io" | |
10 | "os" | 10 | "os" | |
11 | "path/filepath" | 11 | "path" | |
12 | "reflect" | |||
12 | "sort" | 13 | "sort" | |
13 | "strings" | 14 | "strings" | |
14 | "unicode" | 15 | "unicode" | |
15 | ) | 16 | ) | |
16 | 17 | |||
17 | type Error int | 18 | type Error int | |
18 | 19 | |||
19 | const ( | 20 | const ( | |
20 | ENone Error = iota | 21 | ENone Error = iota + 1 | |
21 | EAll | 22 | EAll | |
22 | 23 | |||
23 | // A function or method does not have a corresponding test. | 24 | // A function or method does not have a corresponding test. | |
24 | EMissingTest | 25 | EMissingTest | |
25 | 26 | |||
26 | // The name of a test function does not correspond to a program | 27 | // The name of a test function does not correspond to a program | |
27 | // element to be tested. | 28 | // element to be tested. | |
28 | EMissingTestee | 29 | EMissingTestee | |
29 | 30 | |||
30 | // The tests are not in the same order as their corresponding | 31 | // The tests are not in the same order as their corresponding | |
31 | // testees in the main code. | 32 | // testees in the main code. | |
32 | EOrder | 33 | EOrder | |
33 | 34 | |||
@@ -35,368 +36,428 @@ const ( | @@ -35,368 +36,428 @@ const ( | |||
35 | EName | 36 | EName | |
36 | 37 | |||
37 | // The file of the test method does not correspond to the | 38 | // The file of the test method does not correspond to the | |
38 | // file of the testee. | 39 | // file of the testee. | |
39 | EFile | 40 | EFile | |
40 | ) | 41 | ) | |
41 | 42 | |||
42 | // TestNameChecker ensures that all test names follow a common naming scheme: | 43 | // TestNameChecker ensures that all test names follow a common naming scheme: | |
43 | // Test_${Type}_${Method}__${description_using_underscores} | 44 | // Test_${Type}_${Method}__${description_using_underscores} | |
44 | // Each of the variable parts may be omitted. | 45 | // Each of the variable parts may be omitted. | |
45 | type TestNameChecker struct { | 46 | type TestNameChecker struct { | |
46 | errorf func(format string, args ...interface{}) | 47 | errorf func(format string, args ...interface{}) | |
47 | 48 | |||
48 | ignoredFiles []string | 49 | filters []filter | |
49 | order int | 50 | order int | |
50 | 51 | |||
51 | testees []*testee | 52 | testees []*testee | |
52 | tests []*test | 53 | tests []*test | |
53 | 54 | |||
54 | errorsMask uint64 | 55 | errors []string | |
55 | errors []string | 56 | out io.Writer | |
56 | out io.Writer | |||
57 | } | 57 | } | |
58 | 58 | |||
59 | // NewTestNameChecker creates a new checker. | 59 | // NewTestNameChecker creates a new checker. | |
60 | // By default, all errors are disabled; call Enable to enable them. | 60 | // By default, all errors are enabled; | |
61 | // call Configure to disable them selectively. | |||
61 | func NewTestNameChecker(errorf func(format string, args ...interface{})) *TestNameChecker { | 62 | func NewTestNameChecker(errorf func(format string, args ...interface{})) *TestNameChecker { | |
62 | return &TestNameChecker{errorf: errorf, out: os.Stderr} | 63 | ck := TestNameChecker{errorf: errorf, out: os.Stderr} | |
63 | } | |||
64 | 64 | |||
65 | func (ck *TestNameChecker) IgnoreFiles(fileGlob string) { | 65 | // For test fixtures from https://gopkg.in/check/v1. | |
66 | ck.ignoredFiles = append(ck.ignoredFiles, fileGlob) | 66 | ck.Configure("*_test.go", "*", "SetUpTest", -EMissingTest) | |
67 | } | 67 | ck.Configure("*_test.go", "*", "TearDownTest", -EMissingTest) | |
68 | 68 | |||
69 | func (ck *TestNameChecker) Enable(errors ...Error) { | 69 | // See https://github.com/rillig/gobco. | |
70 | for _, err := range errors { | 70 | ck.Configure("gobco_*.go", "gobco*", "*", -EMissingTest) | |
71 | if err == ENone { | 71 | ck.Configure("gobco_*.go", "", "gobco*", -EMissingTest) | |
72 | ck.errorsMask = 0 | 72 | ||
73 | } else if err == EAll { | 73 | return &ck | |
74 | ck.errorsMask = ^uint64(0) | 74 | } | |
75 | } else if err < 0 { | 75 | ||
76 | ck.errorsMask &= ^(uint64(1) << -uint(err)) | 76 | // Configure sets the errors that are activated for the given code, | |
77 | } else { | 77 | // specified by shell patterns like in path.Match. | |
78 | ck.errorsMask |= uint64(1) << uint(err) | 78 | // | |
79 | } | 79 | // All rules are applied in order. Later rules overwrite earlier rules. | |
80 | } | 80 | // | |
81 | // Individual errors can be enabled by giving their constant and disabled | |||
82 | // by negating them, such as -EMissingTestee. To reset everything, use | |||
83 | // either EAll or ENone. | |||
84 | func (ck *TestNameChecker) Configure(filenames, typeNames, funcNames string, errors ...Error) { | |||
85 | ck.filters = append(ck.filters, filter{filenames, typeNames, funcNames, errors}) | |||
81 | } | 86 | } | |
82 | 87 | |||
83 | func (ck *TestNameChecker) Check() { | 88 | func (ck *TestNameChecker) Check() { | |
84 | ck.load() | 89 | ck.load(".") | |
85 | ck.checkTestees() | 90 | ck.checkTestees() | |
86 | ck.checkTests() | 91 | ck.checkTests() | |
87 | ck.checkOrder() | 92 | ck.checkOrder() | |
88 | ck.print() | 93 | ck.print() | |
89 | } | 94 | } | |
90 | 95 | |||
91 | // load loads all type, function and method names from the current package. | 96 | // load loads all type, function and method names from the current package. | |
92 | func (ck *TestNameChecker) load() { | 97 | func (ck *TestNameChecker) load(dir string) { | |
98 | ||||
93 | fileSet := token.NewFileSet() | 99 | fileSet := token.NewFileSet() | |
94 | pkgs, err := parser.ParseDir(fileSet, ".", nil, 0) | 100 | pkgs, err := parser.ParseDir(fileSet, dir, nil, 0) | |
95 | if err != nil { | 101 | if err != nil { | |
96 | panic(err) | 102 | panic(err) | |
97 | } | 103 | } | |
98 | 104 | |||
99 | var pkgnames []string | 105 | for _, pkgname := range sortedKeys(pkgs) { | |
100 | for pkgname := range pkgs { | |||
101 | pkgnames = append(pkgnames, pkgname) | |||
102 | } | |||
103 | sort.Strings(pkgnames) | |||
104 | ||||
105 | for _, pkgname := range pkgnames { | |||
106 | files := pkgs[pkgname].Files | 106 | files := pkgs[pkgname].Files | |
107 | 107 | |||
108 | var filenames []string | 108 | for _, filename := range sortedKeys(files) { | |
109 | for filename := range files { | |||
110 | filenames = append(filenames, filename) | |||
111 | } | |||
112 | sort.Strings(filenames) | |||
113 | ||||
114 | for _, filename := range filenames { | |||
115 | file := files[filename] | 109 | file := files[filename] | |
116 | for _, decl := range file.Decls { | 110 | for _, decl := range file.Decls { | |
117 | ck.loadDecl(decl, filename) | 111 | ck.loadDecl(decl, filename) | |
118 | } | 112 | } | |
119 | } | 113 | } | |
120 | } | 114 | } | |
121 | 115 | |||
122 | ck.relate() | 116 | ck.relate() | |
123 | } | 117 | } | |
124 | 118 | |||
125 | // loadDecl adds a single type or function declaration to the known elements. | 119 | // loadDecl adds a single type or function declaration to the known elements. | |
126 | func (ck *TestNameChecker) loadDecl(decl ast.Decl, filename string) { | 120 | func (ck *TestNameChecker) loadDecl(decl ast.Decl, filename string) { | |
127 | switch decl := decl.(type) { | 121 | switch decl := decl.(type) { | |
128 | 122 | |||
129 | case *ast.GenDecl: | 123 | case *ast.GenDecl: | |
130 | for _, spec := range decl.Specs { | 124 | for _, spec := range decl.Specs { | |
131 | switch spec := spec.(type) { | 125 | switch spec := spec.(type) { | |
132 | case *ast.TypeSpec: | 126 | case *ast.TypeSpec: | |
133 | typeName := spec.Name.Name | 127 | typeName := spec.Name.Name | |
134 | ck.addCode(code{filename, typeName, "", ck.nextOrder()}) | 128 | ck.addCode(code{filename, typeName, "", 0}) | |
135 | } | 129 | } | |
136 | } | 130 | } | |
137 | 131 | |||
138 | case *ast.FuncDecl: | 132 | case *ast.FuncDecl: | |
139 | typeName := "" | 133 | typeName := "" | |
140 | if decl.Recv != nil { | 134 | if decl.Recv != nil { | |
141 | typeExpr := decl.Recv.List[0].Type.(ast.Expr) | 135 | typeExpr := decl.Recv.List[0].Type.(ast.Expr) | |
142 | if star, ok := typeExpr.(*ast.StarExpr); ok { | 136 | if star, ok := typeExpr.(*ast.StarExpr); ok { | |
143 | typeName = star.X.(*ast.Ident).Name | 137 | typeName = star.X.(*ast.Ident).Name | |
144 | } else { | 138 | } else { | |
145 | typeName = typeExpr.(*ast.Ident).Name | 139 | typeName = typeExpr.(*ast.Ident).Name | |
146 | } | 140 | } | |
147 | } | 141 | } | |
148 | ck.addCode(code{filename, typeName, decl.Name.Name, ck.nextOrder()}) | 142 | funcName := decl.Name.Name | |
143 | ck.addCode(code{filename, typeName, funcName, 0}) | |||
149 | } | 144 | } | |
150 | } | 145 | } | |
151 | 146 | |||
152 | func (ck *TestNameChecker) addCode(code code) { | 147 | func (ck *TestNameChecker) addCode(code code) { | |
153 | isTest := strings.HasSuffix(code.file, "_test.go") && | 148 | if code.isTestScope() && code.isFunc() && code.Func == "TestMain" { | |
154 | code.Type != "" && | 149 | // This is not a test for Main, but a wrapper function of the test. | |
155 | strings.HasPrefix(code.Func, "Test") | 150 | // Therefore it is completely ignored. | |
151 | // See https://golang.org/pkg/testing/#hdr-Main. | |||
152 | // | |||
153 | // Among others, this function is created by | |||
154 | // https://github.com/rillig/gobco when measuring the branch | |||
155 | // coverage of a package. | |||
156 | return | |||
157 | } | |||
156 | 158 | |||
157 | if isTest { | 159 | if !ck.isRelevant(code.file, code.Type, code.Func, EAll) { | |
160 | return | |||
161 | } | |||
162 | ||||
163 | if code.isTest() { | |||
158 | ck.addTest(code) | 164 | ck.addTest(code) | |
159 | } else { | 165 | } else { | |
160 | ck.addTestee(code) | 166 | ck.addTestee(code) | |
161 | } | 167 | } | |
162 | } | 168 | } | |
163 | 169 | |||
164 | func (ck *TestNameChecker) addTestee(code code) { | 170 | func (ck *TestNameChecker) addTestee(code code) { | |
171 | code.order = ck.nextOrder() | |||
165 | ck.testees = append(ck.testees, &testee{code}) | 172 | ck.testees = append(ck.testees, &testee{code}) | |
166 | } | 173 | } | |
167 | 174 | |||
168 | func (ck *TestNameChecker) addTest(code code) { | 175 | func (ck *TestNameChecker) addTest(code code) { | |
169 | if !strings.HasPrefix(code.Func, "Test_") { | 176 | if !strings.HasPrefix(code.Func, "Test_") && | |
177 | code.Func != "Test" && | |||
170 | ck.addError( | 178 | ck.addError( | |
171 | EName, | 179 | EName, | |
180 | code, | |||
172 | "Test %q must start with %q.", | 181 | "Test %q must start with %q.", | |
173 | code.fullName(), "Test_") | 182 | code.fullName(), "Test_") { | |
183 | ||||
174 | return | 184 | return | |
175 | } | 185 | } | |
176 | 186 | |||
177 | parts := strings.SplitN(code.Func, "__", 2) | 187 | parts := strings.SplitN(code.Func, "__", 2) | |
178 | testeeName := strings.TrimPrefix(strings.TrimPrefix(parts[0], "Test"), "_") | 188 | testeeName := strings.TrimPrefix(strings.TrimPrefix(parts[0], "Test"), "_") | |
179 | descr := "" | 189 | descr := "" | |
180 | if len(parts) > 1 { | 190 | if len(parts) > 1 { | |
181 | if parts[1] == "" { | 191 | if parts[1] == "" && | |
182 | ck.addError( | 192 | ck.addError( | |
183 | EName, | 193 | EName, | |
184 | "Test %q must not have a nonempty description.", | 194 | code, | |
185 | code.fullName()) | 195 | "Test %q must have a nonempty description.", | |
196 | code.fullName()) { | |||
186 | return | 197 | return | |
187 | } | 198 | } | |
188 | descr = parts[1] | 199 | descr = parts[1] | |
189 | } | 200 | } | |
190 | 201 | |||
202 | code.order = ck.nextOrder() | |||
191 | ck.tests = append(ck.tests, &test{code, testeeName, descr, nil}) | 203 | ck.tests = append(ck.tests, &test{code, testeeName, descr, nil}) | |
192 | } | 204 | } | |
193 | 205 | |||
194 | func (ck *TestNameChecker) nextOrder() int { | 206 | func (ck *TestNameChecker) nextOrder() int { | |
195 | id := ck.order | 207 | id := ck.order | |
196 | ck.order++ | 208 | ck.order++ | |
197 | return id | 209 | return id | |
198 | } | 210 | } | |
199 | 211 | |||
200 | // relate connects the tests to their testees. | 212 | // relate connects the tests to their testees. | |
201 | func (ck *TestNameChecker) relate() { | 213 | func (ck *TestNameChecker) relate() { | |
202 | testeesByPrefix := make(map[string]*testee) | 214 | testeesByPrefix := make(map[string]*testee) | |
203 | for _, testee := range ck.testees { | 215 | for _, testee := range ck.testees { | |
204 | prefix := join(testee.Type, "_", testee.Func) | 216 | prefix := join(testee.Type, "_", testee.Func) | |
205 | testeesByPrefix[prefix] = testee | 217 | testeesByPrefix[prefix] = testee | |
206 | } | 218 | } | |
207 | 219 | |||
208 | for _, test := range ck.tests { | 220 | for _, test := range ck.tests { | |
209 | test.testee = testeesByPrefix[test.testeeName] | 221 | test.testee = testeesByPrefix[test.testeeName] | |
210 | } | 222 | } | |
211 | } | 223 | } | |
212 | 224 | |||
213 | func (ck *TestNameChecker) checkTests() { | 225 | func (ck *TestNameChecker) checkTests() { | |
214 | for _, test := range ck.tests { | 226 | for _, test := range ck.tests { | |
215 | ck.checkTestFile(test) | 227 | ck.checkTestFile(test) | |
216 | ck.checkTestName(test) | |||
217 | ck.checkTestTestee(test) | 228 | ck.checkTestTestee(test) | |
229 | ck.checkTestDescr(test) | |||
218 | } | 230 | } | |
219 | } | 231 | } | |
220 | 232 | |||
221 | func (ck *TestNameChecker) checkTestFile(test *test) { | 233 | func (ck *TestNameChecker) checkTestFile(test *test) { | |
222 | testee := test.testee | 234 | testee := test.testee | |
223 | if testee == nil || testee.file == test.file { | 235 | if testee == nil || testee.file == test.file { | |
224 | return | 236 | return | |
225 | } | 237 | } | |
226 | 238 | |||
227 | correctTestFile := strings.TrimSuffix(testee.file, ".go") + "_test.go" | 239 | correctTestFile := strings.TrimSuffix(testee.file, ".go") + "_test.go" | |
228 | if correctTestFile != test.file { | 240 | if correctTestFile == test.file { | |
229 | ck.addError( | 241 | return | |
230 | EFile, | |||
231 | "Test %q for %q must be in %s instead of %s.", | |||
232 | test.fullName(), testee.fullName(), correctTestFile, test.file) | |||
233 | } | 242 | } | |
243 | ||||
244 | ck.addError( | |||
245 | EFile, | |||
246 | test.code, | |||
247 | "Test %q for %q must be in %s instead of %s.", | |||
248 | test.fullName(), testee.fullName(), correctTestFile, test.file) | |||
234 | } | 249 | } | |
235 | 250 | |||
236 | func (ck *TestNameChecker) checkTestTestee(test *test) { | 251 | func (ck *TestNameChecker) checkTestTestee(test *test) { | |
237 | testee := test.testee | 252 | testee := test.testee | |
238 | if testee != nil || test.testeeName == "" { | 253 | if testee != nil || test.testeeName == "" { | |
239 | return | 254 | return | |
240 | } | 255 | } | |
241 | 256 | |||
242 | testeeName := strings.Replace(test.testeeName, "_", ".", -1) | 257 | testeeName := strings.Replace(test.testeeName, "_", ".", -1) | |
243 | ck.addError( | 258 | ck.addError( | |
244 | EMissingTestee, | 259 | EMissingTestee, | |
260 | test.code, | |||
245 | "Missing testee %q for test %q.", | 261 | "Missing testee %q for test %q.", | |
246 | testeeName, test.fullName()) | 262 | testeeName, test.fullName()) | |
247 | } | 263 | } | |
248 | 264 | |||
249 | // checkTestName ensures that the method name does not accidentally | 265 | // checkTestDescr ensures that the type or function name of the testee | |
250 | // end up in the description of the test. This could happen if there is a | 266 | // does not accidentally end up in the description of the test. This could | |
251 | // double underscore instead of a single underscore. | 267 | // happen if there is a double underscore instead of a single underscore. | |
252 | func (ck *TestNameChecker) checkTestName(test *test) { | 268 | func (ck *TestNameChecker) checkTestDescr(test *test) { | |
253 | testee := test.testee | 269 | testee := test.testee | |
254 | if testee == nil { | 270 | if testee == nil || testee.isMethod() || !isCamelCase(test.descr) { | |
255 | return | |||
256 | } | |||
257 | if testee.Type != "" && testee.Func != "" { | |||
258 | return | |||
259 | } | |||
260 | if !isCamelCase(test.descr) { | |||
261 | return | 271 | return | |
262 | } | 272 | } | |
263 | 273 | |||
264 | ck.addError( | 274 | ck.addError( | |
265 | EName, | 275 | EName, | |
276 | testee.code, | |||
266 | "%s: Test description %q must not use CamelCase in the first word.", | 277 | "%s: Test description %q must not use CamelCase in the first word.", | |
267 | test.fullName(), test.descr) | 278 | test.fullName(), test.descr) | |
268 | } | 279 | } | |
269 | 280 | |||
270 | func (ck *TestNameChecker) checkTestees() { | 281 | func (ck *TestNameChecker) checkTestees() { | |
271 | tested := make(map[*testee]bool) | 282 | tested := make(map[*testee]bool) | |
272 | for _, test := range ck.tests { | 283 | for _, test := range ck.tests { | |
273 | tested[test.testee] = true | 284 | tested[test.testee] = true | |
274 | } | 285 | } | |
275 | 286 | |||
276 | for _, testee := range ck.testees { | 287 | for _, testee := range ck.testees { | |
277 | if tested[testee] || testee.Func == "" { | 288 | ck.checkTesteeTest(testee, tested) | |
278 | continue | 289 | } | |
279 | } | 290 | } | |
280 | 291 | |||
281 | testName := "Test_" + join(testee.Type, "_", testee.Func) | 292 | func (ck *TestNameChecker) checkTesteeTest(testee *testee, tested map[*testee]bool) { | |
282 | ck.addError( | 293 | if tested[testee] || testee.isType() { | |
283 | EMissingTest, | 294 | return | |
284 | "Missing unit test %q for %q.", | |||
285 | testName, testee.fullName()) | |||
286 | } | 295 | } | |
296 | ||||
297 | testName := "Test_" + join(testee.Type, "_", testee.Func) | |||
298 | ck.addError( | |||
299 | EMissingTest, | |||
300 | testee.code, | |||
301 | "Missing unit test %q for %q.", | |||
302 | testName, testee.fullName()) | |||
287 | } | 303 | } | |
288 | 304 | |||
289 | func (ck *TestNameChecker) isIgnored(filename string) bool { | 305 | // isRelevant checks whether the given error is enabled. | |
290 | for _, mask := range ck.ignoredFiles { | 306 | func (ck *TestNameChecker) isRelevant(filename, typeName, funcName string, e Error) bool { | |
291 | ok, err := filepath.Match(mask, filename) | 307 | mask := ^uint64(0) | |
292 | if err != nil { | 308 | for _, filter := range ck.filters { | |
293 | panic(err) | 309 | if matches(filename, filter.filenames) && | |
310 | matches(typeName, filter.typeNames) && | |||
311 | matches(funcName, filter.funcNames) { | |||
312 | mask = ck.errorsMask(mask, filter.errors...) | |||
294 | } | 313 | } | |
295 | if ok { | 314 | } | |
296 | return true | 315 | return mask&ck.errorsMask(0, e) != 0 | |
316 | } | |||
317 | ||||
318 | func (ck *TestNameChecker) errorsMask(mask uint64, errors ...Error) uint64 { | |||
319 | for _, err := range errors { | |||
320 | if err == ENone { | |||
321 | mask = 0 | |||
322 | } else if err == EAll { | |||
323 | mask = ^uint64(0) | |||
324 | } else if err < 0 { | |||
325 | mask &= ^(uint64(1) << -uint(err)) | |||
326 | } else { | |||
327 | mask |= uint64(1) << uint(err) | |||
297 | } | 328 | } | |
298 | } | 329 | } | |
299 | return false | 330 | return mask | |
300 | } | 331 | } | |
301 | 332 | |||
302 | // checkOrder ensures that the tests appear in the same order as their | 333 | // checkOrder ensures that the tests appear in the same order as their | |
303 | // counterparts in the main code. | 334 | // counterparts in the main code. | |
304 | func (ck *TestNameChecker) checkOrder() { | 335 | func (ck *TestNameChecker) checkOrder() { | |
305 | maxOrderByFile := make(map[string]*test) | 336 | maxOrderByFile := make(map[string]*test) | |
306 | 337 | |||
307 | for _, test := range ck.tests { | 338 | for _, test := range ck.tests { | |
308 | testee := test.testee | 339 | testee := test.testee | |
309 | if testee == nil { | 340 | if testee == nil { | |
310 | continue | 341 | continue | |
311 | } | 342 | } | |
312 | 343 | |||
313 | maxOrder := maxOrderByFile[testee.file] | 344 | maxOrder := maxOrderByFile[testee.file] | |
314 | if maxOrder == nil || testee.order > maxOrder.testee.order { | 345 | if maxOrder == nil || testee.order > maxOrder.testee.order { | |
315 | maxOrderByFile[testee.file] = test | 346 | maxOrderByFile[testee.file] = test | |
316 | } | 347 | } | |
317 | 348 | |||
318 | if maxOrder != nil && testee.order < maxOrder.testee.order { | 349 | if maxOrder != nil && testee.order < maxOrder.testee.order { | |
319 | insertBefore := maxOrder | 350 | insertBefore := maxOrder | |
320 | for _, before := range ck.tests { | 351 | for _, before := range ck.tests { | |
321 | if before.file == test.file && before.testee != nil && before.testee.order > testee.order { | 352 | if before.file == test.file && before.testee != nil && before.testee.order > testee.order { | |
322 | insertBefore = before | 353 | insertBefore = before | |
323 | break | 354 | break | |
324 | } | 355 | } | |
325 | } | 356 | } | |
357 | ||||
326 | ck.addError( | 358 | ck.addError( | |
327 | EOrder, | 359 | EOrder, | |
328 | "Test %q should be ordered before %q.", | 360 | test.code, | |
361 | "Test %q must be ordered before %q.", | |||
329 | test.fullName(), insertBefore.fullName()) | 362 | test.fullName(), insertBefore.fullName()) | |
330 | } | 363 | } | |
331 | } | 364 | } | |
332 | } | 365 | } | |
333 | 366 | |||
334 | func (ck *TestNameChecker) addError(e Error, format string, args ...interface{}) { | 367 | func (ck *TestNameChecker) addError(e Error, c code, format string, args ...interface{}) bool { | |
335 | if ck.errorsMask&(uint64(1)<<uint(e)) != 0 { | 368 | relevant := ck.isRelevant(c.file, c.Type, c.Func, e) | |
369 | if relevant { | |||
336 | ck.errors = append(ck.errors, fmt.Sprintf(format, args...)) | 370 | ck.errors = append(ck.errors, fmt.Sprintf(format, args...)) | |
337 | } | 371 | } | |
372 | return relevant | |||
338 | } | 373 | } | |
339 | 374 | |||
340 | func (ck *TestNameChecker) print() { | 375 | func (ck *TestNameChecker) print() { | |
341 | for _, msg := range ck.errors { | 376 | for _, msg := range ck.errors { | |
342 | _, _ = fmt.Fprintln(ck.out, msg) | 377 | _, _ = fmt.Fprintln(ck.out, msg) | |
343 | } | 378 | } | |
344 | 379 | |||
345 | errors := plural(len(ck.errors), "error", "errors") | 380 | n := len(ck.errors) | |
346 | if len(ck.errors) > 0 { | 381 | if n > 1 { | |
347 | ck.errorf("%s.", errors) | 382 | ck.errorf("%d errors.", n) | |
383 | } else if n == 1 { | |||
384 | ck.errorf("%d error.", n) | |||
348 | } | 385 | } | |
349 | } | 386 | } | |
350 | 387 | |||
351 | type code struct { | 388 | type filter struct { | |
352 | file string // The file containing the code | 389 | filenames string | |
353 | Type string // The type, e.g. MkLine | 390 | typeNames string | |
354 | Func string // The function or method name, e.g. Warnf | 391 | funcNames string | |
355 | order int // The relative order in the file | 392 | errors []Error | |
356 | } | 393 | } | |
357 | 394 | |||
358 | func (c *code) fullName() string { return join(c.Type, ".", c.Func) } | |||
359 | ||||
360 | // testee is an element of the source code that can be tested. | 395 | // testee is an element of the source code that can be tested. | |
361 | // It is either a type, a function or a method. | |||
362 | type testee struct { | 396 | type testee struct { | |
363 | code | 397 | code | |
364 | } | 398 | } | |
365 | 399 | |||
366 | type test struct { | 400 | type test struct { | |
367 | code | 401 | code | |
368 | 402 | |||
369 | testeeName string // The method name without the "Test_" prefix and description | 403 | testeeName string // The method name without the "Test_" prefix and description | |
370 | descr string // The part after the "__" in the method name | 404 | descr string // The part after the "__" in the method name | |
371 | testee *testee | 405 | testee *testee | |
372 | } | 406 | } | |
373 | 407 | |||
374 | func plural(n int, sg, pl string) string { | 408 | // code is either a type, a function or a method. | |
375 | if n == 0 { | 409 | type code struct { | |
376 | return "" | 410 | file string // the file containing the code | |
377 | } | 411 | Type string // the type, e.g. MkLine | |
378 | form := pl | 412 | Func string // the function or method name, e.g. Warnf | |
379 | if n == 1 { | 413 | order int // the relative order in the file | |
380 | form = sg | 414 | } | |
381 | } | 415 | ||
382 | return fmt.Sprintf("%d %s", n, form) | 416 | func (c *code) fullName() string { return join(c.Type, ".", c.Func) } | |
417 | func (c *code) isFunc() bool { return c.Type == "" } | |||
418 | func (c *code) isType() bool { return c.Func == "" } | |||
419 | func (c *code) isMethod() bool { return c.Type != "" && c.Func != "" } | |||
420 | ||||
421 | func (c *code) isTest() bool { | |||
422 | return c.isTestScope() && strings.HasPrefix(c.Func, "Test") | |||
423 | } | |||
424 | func (c *code) isTestScope() bool { | |||
425 | return strings.HasSuffix(c.file, "_test.go") | |||
383 | } | 426 | } | |
384 | 427 | |||
385 | func isCamelCase(str string) bool { | 428 | func isCamelCase(str string) bool { | |
386 | for i := 0; i+1 < len(str); i++ { | 429 | for i := 0; i+1 < len(str); i++ { | |
387 | if str[i] == '_' { | 430 | if str[i] == '_' { | |
388 | return false | 431 | return false | |
389 | } | 432 | } | |
390 | if unicode.IsLower(rune(str[i])) && unicode.IsUpper(rune(str[i+1])) { | 433 | if unicode.IsLower(rune(str[i])) && unicode.IsUpper(rune(str[i+1])) { | |
391 | return true | 434 | return true | |
392 | } | 435 | } | |
393 | } | 436 | } | |
394 | return false | 437 | return false | |
395 | } | 438 | } | |
396 | 439 | |||
397 | func join(a, sep, b string) string { | 440 | func join(a, sep, b string) string { | |
398 | if a == "" || b == "" { | 441 | if a == "" || b == "" { | |
399 | sep = "" | 442 | sep = "" | |
400 | } | 443 | } | |
401 | return a + sep + b | 444 | return a + sep + b | |
402 | } | 445 | } | |
446 | ||||
447 | func matches(subj string, pattern string) bool { | |||
448 | ok, err := path.Match(pattern, subj) | |||
449 | if err != nil { | |||
450 | panic(err) | |||
451 | } | |||
452 | return ok | |||
453 | } | |||
454 | ||||
455 | // sortedKeys returns the sorted keys from an arbitrary map. | |||
456 | func sortedKeys(m interface{}) []string { | |||
457 | var keys []string | |||
458 | for _, key := range reflect.ValueOf(m).MapKeys() { | |||
459 | keys = append(keys, key.Interface().(string)) | |||
460 | } | |||
461 | sort.Strings(keys) | |||
462 | return keys | |||
463 | } |
@@ -1,125 +1,310 @@ | @@ -1,125 +1,310 @@ | |||
1 | package intqa | 1 | package intqa | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | "bytes" | 4 | "bytes" | |
5 | "fmt" | 5 | "fmt" | |
6 | "go/ast" | |||
6 | "gopkg.in/check.v1" | 7 | "gopkg.in/check.v1" | |
7 | "io/ioutil" | 8 | "io/ioutil" | |
9 | "path" | |||
8 | "testing" | 10 | "testing" | |
9 | ) | 11 | ) | |
10 | 12 | |||
11 | type Suite struct { | 13 | type Suite struct { | |
12 | c *check.C | 14 | c *check.C | |
13 | ck *TestNameChecker | 15 | ck *TestNameChecker | |
14 | summary string | 16 | summary string | |
15 | } | 17 | } | |
16 | 18 | |||
17 | func Test(t *testing.T) { | 19 | func Test(t *testing.T) { | |
18 | check.Suite(&Suite{}) | 20 | check.Suite(&Suite{}) | |
19 | check.TestingT(t) | 21 | check.TestingT(t) | |
20 | } | 22 | } | |
21 | 23 | |||
22 | func (s *Suite) Init(c *check.C) *TestNameChecker { | 24 | func (s *Suite) Init(c *check.C) *TestNameChecker { | |
23 | errorf := func(format string, args ...interface{}) { | 25 | errorf := func(format string, args ...interface{}) { | |
24 | s.summary = fmt.Sprintf(format, args...) | 26 | s.summary = fmt.Sprintf(format, args...) | |
25 | } | 27 | } | |
26 | 28 | |||
27 | s.c = c | 29 | s.c = c | |
28 | s.ck = NewTestNameChecker(errorf) | 30 | s.ck = NewTestNameChecker(errorf) | |
29 | s.ck.Enable(EAll) | |||
30 | s.ck.out = ioutil.Discard | 31 | s.ck.out = ioutil.Discard | |
31 | return s.ck | 32 | return s.ck | |
32 | } | 33 | } | |
33 | 34 | |||
34 | func (s *Suite) TearDownTest(c *check.C) { | 35 | func (s *Suite) TearDownTest(c *check.C) { | |
35 | s.c = c | 36 | s.c = c | |
36 | s.CheckErrors(nil...) | 37 | s.CheckErrors(nil...) | |
37 | s.CheckSummary("") | 38 | s.CheckSummary("") | |
38 | } | 39 | } | |
39 | 40 | |||
41 | func (s *Suite) CheckTestees(testees ...*testee) { | |||
42 | s.c.Check(s.ck.testees, check.DeepEquals, testees) | |||
43 | s.ck.testees = nil | |||
44 | } | |||
45 | ||||
46 | func (*Suite) newTestee(filename, typeName, funcName string, order int) *testee { | |||
47 | return &testee{code{filename, typeName, funcName, order}} | |||
48 | } | |||
49 | ||||
50 | func (s *Suite) CheckTests(tests ...*test) { | |||
51 | s.c.Check(s.ck.tests, check.DeepEquals, tests) | |||
52 | s.ck.tests = nil | |||
53 | } | |||
54 | ||||
55 | func (*Suite) newTest(filename, typeName, funcName string, order int, testeeName, descr string, testee *testee) *test { | |||
56 | c := code{filename, typeName, funcName, order} | |||
57 | return &test{c, testeeName, descr, testee} | |||
58 | } | |||
59 | ||||
40 | func (s *Suite) CheckErrors(errors ...string) { | 60 | func (s *Suite) CheckErrors(errors ...string) { | |
41 | s.c.Check(s.ck.errors, check.DeepEquals, errors) | 61 | s.c.Check(s.ck.errors, check.DeepEquals, errors) | |
42 | s.ck.errors = nil | 62 | s.ck.errors = nil | |
43 | } | 63 | } | |
44 | 64 | |||
45 | func (s *Suite) CheckSummary(summary string) { | 65 | func (s *Suite) CheckSummary(summary string) { | |
46 | s.c.Check(s.summary, check.Equals, summary) | 66 | s.c.Check(s.summary, check.Equals, summary) | |
47 | s.summary = "" | 67 | s.summary = "" | |
48 | } | 68 | } | |
49 | 69 | |||
50 | func (s *Suite) Test_TestNameChecker_Enable(c *check.C) { | 70 | func (s *Suite) Test_NewTestNameChecker(c *check.C) { | |
71 | ck := s.Init(c) | |||
72 | ||||
73 | c.Check(ck.isRelevant("*_test.go", "Suite", "SetUpTest", EAll), check.Equals, true) | |||
74 | c.Check(ck.isRelevant("*_test.go", "Suite", "SetUpTest", EMissingTest), check.Equals, false) | |||
75 | ||||
76 | c.Check(ck.isRelevant("*_test.go", "Suite", "TearDownTest", EAll), check.Equals, true) | |||
77 | c.Check(ck.isRelevant("*_test.go", "Suite", "TearDownTest", EMissingTest), check.Equals, false) | |||
78 | } | |||
79 | ||||
80 | func (s *Suite) Test_TestNameChecker_Configure(c *check.C) { | |||
51 | ck := s.Init(c) | 81 | ck := s.Init(c) | |
52 | 82 | |||
53 | ck.Enable(ENone) // overwrite initialization from Suite.Init | 83 | ck.Configure("*", "*", "*", ENone) // overwrite initialization from Suite.Init | |
84 | ||||
85 | c.Check(ck.isRelevant("", "", "", EAll), check.Equals, false) | |||
86 | c.Check(ck.isRelevant("", "", "", EMissingTestee), check.Equals, false) | |||
87 | c.Check(ck.isRelevant("", "", "", EMissingTest), check.Equals, false) | |||
88 | ||||
89 | ck.Configure("*", "*", "*", EAll) | |||
54 | 90 | |||
55 | c.Check(ck.errorsMask, check.Equals, uint64(0)) | 91 | c.Check(ck.isRelevant("", "", "", EAll), check.Equals, true) | |
92 | c.Check(ck.isRelevant("", "", "", EMissingTestee), check.Equals, true) | |||
93 | c.Check(ck.isRelevant("", "", "", EMissingTest), check.Equals, true) | |||
56 | 94 | |||
57 | ck.Enable(EAll) | 95 | ck.Configure("*", "*", "*", -EMissingTestee) | |
58 | 96 | |||
59 | c.Check(ck.errorsMask, check.Equals, ^uint64(0)) | 97 | c.Check(ck.isRelevant("", "", "", EAll), check.Equals, true) | |
98 | c.Check(ck.isRelevant("", "", "", EMissingTestee), check.Equals, false) | |||
99 | c.Check(ck.isRelevant("", "", "", EMissingTest), check.Equals, true) | |||
100 | ||||
101 | ck.Configure("*", "*", "*", ENone, EMissingTest) | |||
102 | ||||
103 | c.Check(ck.isRelevant("", "", "", EAll), check.Equals, true) | |||
104 | c.Check(ck.isRelevant("", "", "", EMissingTestee), check.Equals, false) | |||
105 | c.Check(ck.isRelevant("", "", "", EMissingTest), check.Equals, true) | |||
106 | ||||
107 | ck.Configure("*", "*", "*", EAll, -EMissingTest) | |||
108 | ||||
109 | c.Check(ck.isRelevant("", "", "", EAll), check.Equals, true) | |||
110 | c.Check(ck.isRelevant("", "", "", EMissingTestee), check.Equals, true) | |||
111 | c.Check(ck.isRelevant("", "", "", EMissingTest), check.Equals, false) | |||
112 | } | |||
60 | 113 | |||
61 | ck.Enable(ENone, EMissingTest) | 114 | func (s *Suite) Test_TestNameChecker_Configure__ignore_single_function(c *check.C) { | |
115 | ck := s.Init(c) | |||
62 | 116 | |||
63 | c.Check(ck.errorsMask, check.Equals, uint64(4)) | 117 | ck.Configure("*", "*", "*", EAll) | |
64 | 118 | |||
65 | ck.Enable(EAll, -EMissingTest) | 119 | // The intention of this rule is that this particular function is ignored. | |
120 | // Everything else from that file is still processed. | |||
121 | ck.Configure("*_test.go", "", "TestMain", ENone) | |||
66 | 122 | |||
67 | c.Check(ck.errorsMask, check.Equals, ^uint64(0)^4) | 123 | c.Check(ck.isRelevant("file_test.go", "", "", EAll), check.Equals, true) | |
124 | c.Check(ck.isRelevant("file_test.go", "*", "*", EAll), check.Equals, true) | |||
125 | c.Check(ck.isRelevant("file_test.go", "*", "Other", EAll), check.Equals, true) | |||
126 | c.Check(ck.isRelevant("file_test.go", "", "TestMain", EAll), check.Equals, false) | |||
127 | c.Check(ck.isRelevant("file_test.go", "*", "TestMain", EAll), check.Equals, true) | |||
68 | } | 128 | } | |
69 | 129 | |||
70 | func (s *Suite) Test_TestNameChecker_Check(c *check.C) { | 130 | func (s *Suite) Test_TestNameChecker_Check(c *check.C) { | |
71 | ck := s.Init(c) | 131 | ck := s.Init(c) | |
72 | 132 | |||
133 | ck.Configure("*", "Suite", "*", -EMissingTest) | |||
134 | ||||
73 | ck.Check() | 135 | ck.Check() | |
74 | 136 | |||
75 | s.CheckErrors( | 137 | s.CheckErrors( | |
76 | "Missing unit test \"Test_NewTestNameChecker\" for \"NewTestNameChecker\".", | |||
77 | "Missing unit test \"Test_TestNameChecker_IgnoreFiles\" for \"TestNameChecker.IgnoreFiles\".", | |||
78 | "Missing unit test \"Test_TestNameChecker_load\" for \"TestNameChecker.load\".", | |||
79 | "Missing unit test \"Test_TestNameChecker_loadDecl\" for \"TestNameChecker.loadDecl\".", | |||
80 | "Missing unit test \"Test_TestNameChecker_addCode\" for \"TestNameChecker.addCode\".", | 138 | "Missing unit test \"Test_TestNameChecker_addCode\" for \"TestNameChecker.addCode\".", | |
81 | "Missing unit test \"Test_TestNameChecker_addTestee\" for \"TestNameChecker.addTestee\".", | |||
82 | "Missing unit test \"Test_TestNameChecker_nextOrder\" for \"TestNameChecker.nextOrder\".", | |||
83 | "Missing unit test \"Test_TestNameChecker_relate\" for \"TestNameChecker.relate\".", | 139 | "Missing unit test \"Test_TestNameChecker_relate\" for \"TestNameChecker.relate\".", | |
84 | "Missing unit test \"Test_TestNameChecker_checkTests\" for \"TestNameChecker.checkTests\".", | 140 | "Missing unit test \"Test_TestNameChecker_isRelevant\" for \"TestNameChecker.isRelevant\".") | |
85 | "Missing unit test \"Test_TestNameChecker_checkTestees\" for \"TestNameChecker.checkTestees\".", | 141 | s.CheckSummary("3 errors.") | |
86 | "Missing unit test \"Test_TestNameChecker_isIgnored\" for \"TestNameChecker.isIgnored\".", | 142 | } | |
87 | "Missing unit test \"Test_TestNameChecker_addError\" for \"TestNameChecker.addError\".", | 143 | ||
88 | "Missing unit test \"Test_Test\" for \"Test\".", | 144 | func (s *Suite) Test_TestNameChecker_load__filtered_nothing(c *check.C) { | |
89 | "Missing unit test \"Test_Suite_Init\" for \"Suite.Init\".", | 145 | ck := s.Init(c) | |
90 | "Missing unit test \"Test_Suite_TearDownTest\" for \"Suite.TearDownTest\".", | 146 | ||
91 | "Missing unit test \"Test_Suite_CheckErrors\" for \"Suite.CheckErrors\".", | 147 | ck.Configure("*", "*", "*", ENone) | |
92 | "Missing unit test \"Test_Suite_CheckSummary\" for \"Suite.CheckSummary\".", | 148 | ||
93 | "Missing unit test \"Test_Value_Method\" for \"Value.Method\".") | 149 | ck.load(".") | |
94 | s.CheckSummary("18 errors.") | 150 | ||
151 | c.Check(ck.testees, check.IsNil) | |||
152 | c.Check(ck.tests, check.IsNil) | |||
153 | } | |||
154 | ||||
155 | func (s *Suite) Test_TestNameChecker_load__filtered_only_Value(c *check.C) { | |||
156 | ck := s.Init(c) | |||
157 | ||||
158 | ck.Configure("*", "*", "*", ENone) | |||
159 | ck.Configure("*", "Value", "*", EAll) | |||
160 | ||||
161 | ck.load(".") | |||
162 | ||||
163 | c.Check(ck.testees, check.DeepEquals, []*testee{ | |||
164 | {code{"testnames_test.go", "Value", "", 0}}, | |||
165 | {code{"testnames_test.go", "Value", "Method", 1}}}) | |||
166 | c.Check(ck.tests, check.IsNil) | |||
167 | } | |||
168 | ||||
169 | func (s *Suite) Test_TestNameChecker_load__panic(c *check.C) { | |||
170 | ck := s.Init(c) | |||
171 | ||||
172 | c.Check( | |||
173 | func() { ck.load("does-not-exist") }, | |||
174 | check.PanicMatches, | |||
175 | `^open does-not-exist\b.*`) | |||
176 | } | |||
177 | ||||
178 | func (s *Suite) Test_TestNameChecker_loadDecl(c *check.C) { | |||
179 | ck := s.Init(c) | |||
180 | ||||
181 | typeDecl := func(name string) *ast.GenDecl { | |||
182 | return &ast.GenDecl{Specs: []ast.Spec{&ast.TypeSpec{Name: &ast.Ident{Name: name}}}} | |||
183 | } | |||
184 | funcDecl := func(name string) *ast.FuncDecl { | |||
185 | return &ast.FuncDecl{Name: &ast.Ident{Name: name}} | |||
186 | } | |||
187 | methodDecl := func(typeName, methodName string) *ast.FuncDecl { | |||
188 | return &ast.FuncDecl{ | |||
189 | Name: &ast.Ident{Name: methodName}, | |||
190 | Recv: &ast.FieldList{List: []*ast.Field{{Type: &ast.Ident{Name: typeName}}}}} | |||
191 | } | |||
192 | ||||
193 | ck.loadDecl(typeDecl("TypeName"), "file_test.go") | |||
194 | ||||
195 | s.CheckTestees( | |||
196 | s.newTestee("file_test.go", "TypeName", "", 0)) | |||
197 | ||||
198 | // The freestanding TestMain function is ignored by a hardcoded rule, | |||
199 | // independently of the configuration. | |||
200 | ck.loadDecl(funcDecl("TestMain"), "file_test.go") | |||
201 | ||||
202 | // The TestMain method on a type is relevant, but violates the naming rule. | |||
203 | // Therefore it is ignored. | |||
204 | ck.loadDecl(methodDecl("Suite", "TestMain"), "file_test.go") | |||
205 | ||||
206 | s.CheckTests( | |||
207 | nil...) | |||
208 | s.CheckErrors( | |||
209 | "Test \"Suite.TestMain\" must start with \"Test_\".") | |||
210 | ||||
211 | // The above error can be disabled, and then the method is handled | |||
212 | // like any other test method. | |||
213 | ck.Configure("*", "Suite", "*", -EName) | |||
214 | ck.loadDecl(methodDecl("Suite", "TestMain"), "file_test.go") | |||
215 | ||||
216 | s.CheckTests( | |||
217 | s.newTest("file_test.go", "Suite", "TestMain", 1, "Main", "", nil)) | |||
218 | ||||
219 | // There is no special handling for TestMain method with a description. | |||
220 | ck.loadDecl(methodDecl("Suite", "TestMain__descr"), "file_test.go") | |||
221 | ||||
222 | s.CheckTests( | |||
223 | s.newTest("file_test.go", "Suite", "TestMain__descr", 2, "Main", "descr", nil)) | |||
224 | } | |||
225 | ||||
226 | func (s *Suite) Test_TestNameChecker_addTestee(c *check.C) { | |||
227 | ck := s.Init(c) | |||
228 | ||||
229 | code := code{"filename.go", "Type", "Method", 0} | |||
230 | ck.addTestee(code) | |||
231 | ||||
232 | c.Check(ck.testees, check.DeepEquals, []*testee{{code}}) | |||
95 | } | 233 | } | |
96 | 234 | |||
97 | func (s *Suite) Test_TestNameChecker_addTest(c *check.C) { | 235 | func (s *Suite) Test_TestNameChecker_addTest(c *check.C) { | |
98 | ck := s.Init(c) | 236 | ck := s.Init(c) | |
99 | 237 | |||
100 | ck.addTest(code{"filename.go", "Type", "Method", 0}) | 238 | ck.addTest(code{"filename.go", "Type", "Method", 0}) | |
101 | 239 | |||
240 | c.Check(ck.tests, check.IsNil) | |||
102 | s.CheckErrors( | 241 | s.CheckErrors( | |
103 | "Test \"Type.Method\" must start with \"Test_\".") | 242 | "Test \"Type.Method\" must start with \"Test_\".") | |
104 | } | 243 | } | |
105 | 244 | |||
106 | func (s *Suite) Test_TestNameChecker_addTest__empty_description(c *check.C) { | 245 | func (s *Suite) Test_TestNameChecker_addTest__empty_description(c *check.C) { | |
107 | ck := s.Init(c) | 246 | ck := s.Init(c) | |
108 | 247 | |||
109 | ck.addTest(code{"filename.go", "Suite", "Test_Method__", 0}) | 248 | ck.addTest(code{"f_test.go", "Suite", "Test_Method__", 0}) | |
249 | ||||
250 | s.CheckErrors( | |||
251 | "Test \"Suite.Test_Method__\" must have a nonempty description.") | |||
252 | ||||
253 | // The test is not registered and thus cannot complain about its missing | |||
254 | // testee. | |||
255 | ck.checkTests() | |||
256 | ||||
257 | s.CheckErrors( | |||
258 | nil...) | |||
259 | } | |||
260 | ||||
261 | func (s *Suite) Test_TestNameChecker_addTest__suppressed_empty_description(c *check.C) { | |||
262 | ck := s.Init(c) | |||
263 | ||||
264 | ck.Configure("*", "*", "*", -EName) | |||
265 | ck.addTest(code{"f_test.go", "Suite", "Test_Method__", 0}) | |||
266 | ||||
267 | s.CheckErrors( | |||
268 | nil...) | |||
269 | ||||
270 | // Since there was no error above, the test is added normally | |||
271 | // and can complain about its missing testee. | |||
272 | ck.checkTests() | |||
273 | ||||
274 | s.CheckErrors( | |||
275 | "Missing testee \"Method\" for test \"Suite.Test_Method__\".") | |||
276 | } | |||
277 | ||||
278 | func (s *Suite) Test_TestNameChecker_nextOrder(c *check.C) { | |||
279 | ck := s.Init(c) | |||
280 | ||||
281 | c.Check(ck.nextOrder(), check.Equals, 0) | |||
282 | c.Check(ck.nextOrder(), check.Equals, 1) | |||
283 | c.Check(ck.nextOrder(), check.Equals, 2) | |||
284 | } | |||
285 | ||||
286 | func (s *Suite) Test_TestNameChecker_checkTests(c *check.C) { | |||
287 | ck := s.Init(c) | |||
288 | ||||
289 | ck.tests = append(ck.tests, | |||
290 | s.newTest("wrong_test.go", "", "Test_Func", 0, "Func", "", | |||
291 | s.newTestee("source.go", "", "Func", 1))) | |||
292 | ||||
293 | ck.checkTests() | |||
110 | 294 | |||
111 | s.CheckErrors( | 295 | s.CheckErrors( | |
112 | "Test \"Suite.Test_Method__\" must not have a nonempty description.") | 296 | "Test \"Test_Func\" for \"Func\" " + | |
297 | "must be in source_test.go instead of wrong_test.go.") | |||
113 | } | 298 | } | |
114 | 299 | |||
115 | func (s *Suite) Test_TestNameChecker_checkTestFile__global(c *check.C) { | 300 | func (s *Suite) Test_TestNameChecker_checkTestFile__global(c *check.C) { | |
116 | ck := s.Init(c) | 301 | ck := s.Init(c) | |
117 | 302 | |||
118 | ck.checkTestFile(&test{ | 303 | ck.checkTestFile(&test{ | |
119 | code{"demo_test.go", "Suite", "Test__Global", 0}, | 304 | code{"demo_test.go", "Suite", "Test__Global", 0}, | |
120 | "", | 305 | "", | |
121 | "", | 306 | "", | |
122 | &testee{code{"other.go", "", "Global", 0}}}) | 307 | &testee{code{"other.go", "", "Global", 0}}}) | |
123 | 308 | |||
124 | s.CheckErrors( | 309 | s.CheckErrors( | |
125 | "Test \"Suite.Test__Global\" for \"Global\" " + | 310 | "Test \"Suite.Test__Global\" for \"Global\" " + | |
@@ -155,126 +340,289 @@ func (s *Suite) Test_TestNameChecker_che | @@ -155,126 +340,289 @@ func (s *Suite) Test_TestNameChecker_che | |||
155 | func (s *Suite) Test_TestNameChecker_checkTestTestee__testee_exists(c *check.C) { | 340 | func (s *Suite) Test_TestNameChecker_checkTestTestee__testee_exists(c *check.C) { | |
156 | ck := s.Init(c) | 341 | ck := s.Init(c) | |
157 | 342 | |||
158 | ck.checkTestTestee(&test{ | 343 | ck.checkTestTestee(&test{ | |
159 | code{"demo_test.go", "Suite", "Test_Missing", 0}, | 344 | code{"demo_test.go", "Suite", "Test_Missing", 0}, | |
160 | "Missing", | 345 | "Missing", | |
161 | "", | 346 | "", | |
162 | &testee{}}) | 347 | &testee{}}) | |
163 | 348 | |||
164 | s.CheckErrors( | 349 | s.CheckErrors( | |
165 | nil...) | 350 | nil...) | |
166 | } | 351 | } | |
167 | 352 | |||
168 | func (s *Suite) Test_TestNameChecker_checkTestName__camel_case(c *check.C) { | 353 | func (s *Suite) Test_TestNameChecker_checkTestDescr__camel_case(c *check.C) { | |
169 | ck := s.Init(c) | 354 | ck := s.Init(c) | |
170 | 355 | |||
171 | ck.checkTestName(&test{ | 356 | ck.checkTestDescr(&test{ | |
172 | code{"demo_test.go", "Suite", "Test_Missing__CamelCase", 0}, | 357 | code{"demo_test.go", "Suite", "Test_Missing__CamelCase", 0}, | |
173 | "Missing", | 358 | "Missing", | |
174 | "CamelCase", | 359 | "CamelCase", | |
175 | &testee{}}) | 360 | &testee{}}) | |
176 | 361 | |||
177 | s.CheckErrors( | 362 | s.CheckErrors( | |
178 | "Suite.Test_Missing__CamelCase: Test description \"CamelCase\" " + | 363 | "Suite.Test_Missing__CamelCase: Test description \"CamelCase\" " + | |
179 | "must not use CamelCase in the first word.") | 364 | "must not use CamelCase in the first word.") | |
180 | } | 365 | } | |
181 | 366 | |||
367 | func (s *Suite) Test_TestNameChecker_checkTestees(c *check.C) { | |||
368 | ck := s.Init(c) | |||
369 | ||||
370 | ck.testees = []*testee{s.newTestee("s.go", "", "Func", 0)} | |||
371 | ck.tests = nil // force an error | |||
372 | ||||
373 | ck.checkTestees() | |||
374 | ||||
375 | s.CheckErrors( | |||
376 | "Missing unit test \"Test_Func\" for \"Func\".") | |||
377 | } | |||
378 | ||||
379 | func (s *Suite) Test_TestNameChecker_checkTesteeTest(c *check.C) { | |||
380 | ck := s.Init(c) | |||
381 | ||||
382 | ck.checkTesteeTest( | |||
383 | &testee{code{"demo.go", "Type", "", 0}}, | |||
384 | nil) | |||
385 | ck.checkTesteeTest( | |||
386 | &testee{code{"demo.go", "", "Func", 0}}, | |||
387 | nil) | |||
388 | ck.checkTesteeTest( | |||
389 | &testee{code{"demo.go", "Type", "Method", 0}}, | |||
390 | nil) | |||
391 | ||||
392 | s.CheckErrors( | |||
393 | "Missing unit test \"Test_Func\" for \"Func\".", | |||
394 | "Missing unit test \"Test_Type_Method\" for \"Type.Method\".") | |||
395 | } | |||
396 | ||||
397 | func (s *Suite) Test_TestNameChecker_errorsMask(c *check.C) { | |||
398 | ck := s.Init(c) | |||
399 | ||||
400 | c.Check(ck.errorsMask(0, EAll), check.Equals, ^uint64(0)) | |||
401 | c.Check(ck.errorsMask(12345, ENone), check.Equals, uint64(0)) | |||
402 | c.Check(ck.errorsMask(12345, ENone, EMissingTest), check.Equals, uint64(8)) | |||
403 | c.Check(ck.errorsMask(2, EMissingTest), check.Equals, uint64(10)) | |||
404 | } | |||
405 | ||||
182 | func (s *Suite) Test_TestNameChecker_checkOrder(c *check.C) { | 406 | func (s *Suite) Test_TestNameChecker_checkOrder(c *check.C) { | |
183 | ck := s.Init(c) | 407 | ck := s.Init(c) | |
184 | 408 | |||
185 | ck.addTestee(code{"f.go", "T", "", 10}) | 409 | ck.addTestee(code{"f.go", "T", "", 10}) | |
186 | ck.addTestee(code{"f.go", "T", "M1", 11}) | 410 | ck.addTestee(code{"f.go", "T", "M1", 11}) | |
187 | ck.addTestee(code{"f.go", "T", "M2", 12}) | 411 | ck.addTestee(code{"f.go", "T", "M2", 12}) | |
188 | ck.addTestee(code{"f.go", "T", "M3", 13}) | 412 | ck.addTestee(code{"f.go", "T", "M3", 13}) | |
413 | ck.addTest(code{"a_test.go", "S", "Test_A", 98}) // different file, is skipped | |||
414 | ck.addTest(code{"f_test.go", "S", "Test_Missing", 99}) // missing testee, is skipped | |||
189 | ck.addTest(code{"f_test.go", "S", "Test_T_M1", 100}) // maxTestee = 11 | 415 | ck.addTest(code{"f_test.go", "S", "Test_T_M1", 100}) // maxTestee = 11 | |
190 | ck.addTest(code{"f_test.go", "S", "Test_T_M2", 101}) // maxTestee = 12 | 416 | ck.addTest(code{"f_test.go", "S", "Test_T_M2", 101}) // maxTestee = 12 | |
191 | ck.addTest(code{"f_test.go", "S", "Test_T", 102}) // testee 10 < maxTestee 12: insert before first [.testee > testee 10] == T_M1 | 417 | ck.addTest(code{"f_test.go", "S", "Test_T", 102}) // testee 10 < maxTestee 12: insert before first [.testee > testee 10] == T_M1 | |
192 | ck.addTest(code{"f_test.go", "S", "Test_T_M3", 103}) // maxTestee = 13 | 418 | ck.addTest(code{"f_test.go", "S", "Test_T_M3", 103}) // maxTestee = 13 | |
193 | ck.addTest(code{"f_test.go", "S", "Test_T__1", 104}) // testee < maxTestee: insert before first [testee > 10] | 419 | ck.addTest(code{"f_test.go", "S", "Test_T__1", 104}) // testee < maxTestee: insert before first [testee > 10] | |
194 | ck.addTest(code{"f_test.go", "S", "Test_T__2", 105}) // testee < maxTestee: insert before first [testee > 10] | 420 | ck.addTest(code{"f_test.go", "S", "Test_T__2", 105}) // testee < maxTestee: insert before first [testee > 10] | |
195 | ck.addTest(code{"f_test.go", "S", "Test_T_M2__1", 106}) // testee < maxTestee: insert before first [testee > 12] == T_M3 | 421 | ck.addTest(code{"f_test.go", "S", "Test_T_M2__1", 106}) // testee < maxTestee: insert before first [testee > 12] == T_M3 | |
196 | ck.relate() | 422 | ck.relate() | |
197 | 423 | |||
198 | ck.checkOrder() | 424 | ck.checkOrder() | |
199 | 425 | |||
200 | s.CheckErrors( | 426 | s.CheckErrors( | |
201 | "Test \"S.Test_T\" should be ordered before \"S.Test_T_M1\".", | 427 | "Test \"S.Test_T\" must be ordered before \"S.Test_T_M1\".", | |
202 | "Test \"S.Test_T__1\" should be ordered before \"S.Test_T_M1\".", | 428 | "Test \"S.Test_T__1\" must be ordered before \"S.Test_T_M1\".", | |
203 | "Test \"S.Test_T__2\" should be ordered before \"S.Test_T_M1\".", | 429 | "Test \"S.Test_T__2\" must be ordered before \"S.Test_T_M1\".", | |
204 | "Test \"S.Test_T_M2__1\" should be ordered before \"S.Test_T_M3\".") | 430 | "Test \"S.Test_T_M2__1\" must be ordered before \"S.Test_T_M3\".") | |
431 | } | |||
432 | ||||
433 | func (s *Suite) Test_TestNameChecker_addError(c *check.C) { | |||
434 | ck := s.Init(c) | |||
435 | ||||
436 | ck.Configure("ignored*", "*", "*", -EName) | |||
437 | ok1 := ck.addError(EName, code{"ignored.go", "", "Func", 0}, "E1") | |||
438 | ok2 := ck.addError(EName, code{"reported.go", "", "Func", 0}, "E2") | |||
439 | ||||
440 | c.Check(ok1, check.Equals, false) | |||
441 | c.Check(ok2, check.Equals, true) | |||
442 | s.CheckErrors( | |||
443 | "E2") | |||
205 | } | 444 | } | |
206 | 445 | |||
207 | func (s *Suite) Test_TestNameChecker_print__empty(c *check.C) { | 446 | func (s *Suite) Test_TestNameChecker_print__empty(c *check.C) { | |
208 | var out bytes.Buffer | 447 | var out bytes.Buffer | |
209 | ck := s.Init(c) | 448 | ck := s.Init(c) | |
210 | ck.out = &out | 449 | ck.out = &out | |
211 | 450 | |||
212 | ck.print() | 451 | ck.print() | |
213 | 452 | |||
214 | c.Check(out.String(), check.Equals, "") | 453 | c.Check(out.String(), check.Equals, "") | |
215 | } | 454 | } | |
216 | 455 | |||
217 | func (s *Suite) Test_TestNameChecker_print__errors(c *check.C) { | 456 | func (s *Suite) Test_TestNameChecker_print__1_error(c *check.C) { | |
218 | var out bytes.Buffer | 457 | var out bytes.Buffer | |
219 | ck := s.Init(c) | 458 | ck := s.Init(c) | |
220 | ck.out = &out | 459 | ck.out = &out | |
460 | ck.addError(EName, code{}, "1") | |||
221 | 461 | |||
222 | ck.addError(EName, "1") | |||
223 | ck.print() | 462 | ck.print() | |
224 | 463 | |||
225 | c.Check(out.String(), check.Equals, "1\n") | 464 | c.Check(out.String(), check.Equals, "1\n") | |
226 | s.CheckErrors("1") | 465 | s.CheckErrors("1") | |
227 | s.CheckSummary("1 error.") | 466 | s.CheckSummary("1 error.") | |
228 | } | 467 | } | |
229 | 468 | |||
469 | func (s *Suite) Test_TestNameChecker_print__2_errors(c *check.C) { | |||
470 | var out bytes.Buffer | |||
471 | ck := s.Init(c) | |||
472 | ck.out = &out | |||
473 | ck.addError(EName, code{}, "1") | |||
474 | ck.addError(EName, code{}, "2") | |||
475 | ||||
476 | ck.print() | |||
477 | ||||
478 | c.Check(out.String(), check.Equals, "1\n2\n") | |||
479 | s.CheckErrors("1", "2") | |||
480 | s.CheckSummary("2 errors.") | |||
481 | } | |||
482 | ||||
230 | func (s *Suite) Test_code_fullName(c *check.C) { | 483 | func (s *Suite) Test_code_fullName(c *check.C) { | |
231 | _ = s.Init(c) | 484 | _ = s.Init(c) | |
232 | 485 | |||
233 | test := func(typeName, funcName, fullName string) { | 486 | test := func(typeName, funcName, fullName string) { | |
234 | code := code{"filename", typeName, funcName, 0} | 487 | code := code{"filename", typeName, funcName, 0} | |
235 | c.Check(code.fullName(), check.Equals, fullName) | 488 | c.Check(code.fullName(), check.Equals, fullName) | |
236 | } | 489 | } | |
237 | 490 | |||
238 | test("Type", "", "Type") | 491 | test("Type", "", "Type") | |
239 | test("", "Func", "Func") | 492 | test("", "Func", "Func") | |
240 | test("Type", "Method", "Type.Method") | 493 | test("Type", "Method", "Type.Method") | |
241 | } | 494 | } | |
242 | 495 | |||
243 | func (s *Suite) Test_plural(c *check.C) { | 496 | func (s *Suite) Test_code_isFunc(c *check.C) { | |
244 | _ = s.Init(c) | 497 | _ = s.Init(c) | |
245 | 498 | |||
246 | c.Check(plural(0, "singular", "plural"), check.Equals, "") | 499 | test := func(typeName, funcName string, isFunc bool) { | |
247 | c.Check(plural(1, "singular", "plural"), check.Equals, "1 singular") | 500 | code := code{"filename", typeName, funcName, 0} | |
248 | c.Check(plural(2, "singular", "plural"), check.Equals, "2 plural") | 501 | c.Check(code.isFunc(), check.Equals, isFunc) | |
249 | c.Check(plural(1000, "singular", "plural"), check.Equals, "1000 plural") | 502 | } | |
503 | ||||
504 | test("Type", "", false) | |||
505 | test("", "Func", true) | |||
506 | test("Type", "Method", false) | |||
507 | } | |||
508 | ||||
509 | func (s *Suite) Test_code_isType(c *check.C) { | |||
510 | _ = s.Init(c) | |||
511 | ||||
512 | test := func(typeName, funcName string, isType bool) { | |||
513 | code := code{"filename", typeName, funcName, 0} | |||
514 | c.Check(code.isType(), check.Equals, isType) | |||
515 | } | |||
516 | ||||
517 | test("Type", "", true) | |||
518 | test("", "Func", false) | |||
519 | test("Type", "Method", false) | |||
520 | } | |||
521 | ||||
522 | func (s *Suite) Test_code_isMethod(c *check.C) { | |||
523 | _ = s.Init(c) | |||
524 | ||||
525 | test := func(typeName, funcName string, isMethod bool) { | |||
526 | code := code{"filename", typeName, funcName, 0} | |||
527 | c.Check(code.isMethod(), check.Equals, isMethod) | |||
528 | } | |||
529 | ||||
530 | test("Type", "", false) | |||
531 | test("", "Func", false) | |||
532 | test("Type", "Method", true) | |||
533 | } | |||
534 | ||||
535 | func (s *Suite) Test_code_isTest(c *check.C) { | |||
536 | _ = s.Init(c) | |||
537 | ||||
538 | test := func(filename, typeName, funcName string, isTest bool) { | |||
539 | code := code{filename, typeName, funcName, 0} | |||
540 | c.Check(code.isTest(), check.Equals, isTest) | |||
541 | } | |||
542 | ||||
543 | test("f.go", "Type", "", false) | |||
544 | test("f.go", "", "Func", false) | |||
545 | test("f.go", "Type", "Method", false) | |||
546 | test("f.go", "Type", "Test", false) | |||
547 | test("f.go", "Type", "Test_Type_Method", false) | |||
548 | test("f.go", "", "Test_Type_Method", false) | |||
549 | test("f_test.go", "Type", "Test", true) | |||
550 | test("f_test.go", "Type", "Test_Type_Method", true) | |||
551 | test("f_test.go", "", "Test_Type_Method", true) | |||
552 | } | |||
553 | ||||
554 | func (s *Suite) Test_code_isTestScope(c *check.C) { | |||
555 | _ = s.Init(c) | |||
556 | ||||
557 | test := func(filename string, isTestScope bool) { | |||
558 | code := code{filename, "", "", 0} | |||
559 | c.Check(code.isTestScope(), check.Equals, isTestScope) | |||
560 | } | |||
561 | ||||
562 | test("f.go", false) | |||
563 | test("test.go", false) | |||
564 | test("_test.go", true) | |||
565 | test("file_test.go", true) | |||
566 | test("file_linux_test.go", true) | |||
250 | } | 567 | } | |
251 | 568 | |||
252 | func (s *Suite) Test_isCamelCase(c *check.C) { | 569 | func (s *Suite) Test_isCamelCase(c *check.C) { | |
253 | _ = s.Init(c) | 570 | _ = s.Init(c) | |
254 | 571 | |||
255 | c.Check(isCamelCase(""), check.Equals, false) | 572 | c.Check(isCamelCase(""), check.Equals, false) | |
256 | c.Check(isCamelCase("Word"), check.Equals, false) | 573 | c.Check(isCamelCase("Word"), check.Equals, false) | |
257 | c.Check(isCamelCase("Ada_Case"), check.Equals, false) | 574 | c.Check(isCamelCase("Ada_Case"), check.Equals, false) | |
258 | c.Check(isCamelCase("snake_case"), check.Equals, false) | 575 | c.Check(isCamelCase("snake_case"), check.Equals, false) | |
259 | c.Check(isCamelCase("CamelCase"), check.Equals, true) | 576 | c.Check(isCamelCase("CamelCase"), check.Equals, true) | |
260 | 577 | |||
261 | // After the first underscore of the description, any CamelCase | 578 | // After the first underscore of the description, any CamelCase | |
262 | // is ignored because there is no danger of confusing the method | 579 | // is ignored because there is no danger of confusing the method | |
263 | // name with the description. | 580 | // name with the description. | |
264 | c.Check(isCamelCase("Word_CamelCase"), check.Equals, false) | 581 | c.Check(isCamelCase("Word_CamelCase"), check.Equals, false) | |
265 | } | 582 | } | |
266 | 583 | |||
267 | func (s *Suite) Test_join(c *check.C) { | 584 | func (s *Suite) Test_join(c *check.C) { | |
268 | _ = s.Init(c) | 585 | _ = s.Init(c) | |
269 | 586 | |||
270 | c.Check(join("", " and ", ""), check.Equals, "") | 587 | c.Check(join("", " and ", ""), check.Equals, "") | |
271 | c.Check(join("one", " and ", ""), check.Equals, "one") | 588 | c.Check(join("one", " and ", ""), check.Equals, "one") | |
272 | c.Check(join("", " and ", "two"), check.Equals, "two") | 589 | c.Check(join("", " and ", "two"), check.Equals, "two") | |
273 | c.Check(join("one", " and ", "two"), check.Equals, "one and two") | 590 | c.Check(join("one", " and ", "two"), check.Equals, "one and two") | |
274 | } | 591 | } | |
275 | 592 | |||
593 | func (s *Suite) Test_matches(c *check.C) { | |||
594 | _ = s.Init(c) | |||
595 | ||||
596 | c.Check(matches("*", "*"), check.Equals, true) | |||
597 | c.Check(matches("anything", "*"), check.Equals, true) | |||
598 | c.Check(matches("*", "anything"), check.Equals, false) | |||
599 | c.Check(func() { matches("any", "[") }, check.Panics, path.ErrBadPattern) | |||
600 | } | |||
601 | ||||
602 | func (s *Suite) Test_sortedKeys(c *check.C) { | |||
603 | _ = s.Init(c) | |||
604 | ||||
605 | m := make(map[string]uint8) | |||
606 | m["first"] = 1 | |||
607 | m["second"] = 2 | |||
608 | m["third"] = 3 | |||
609 | m["fourth"] = 4 | |||
610 | ||||
611 | c.Check( | |||
612 | sortedKeys(m), | |||
613 | check.DeepEquals, | |||
614 | []string{"first", "fourth", "second", "third"}) | |||
615 | } | |||
616 | ||||
617 | func (s *Suite) Test_Value_Method(c *check.C) { | |||
618 | _ = s.Init(c) | |||
619 | ||||
620 | // Just for code coverage of checkTestFile, to have a piece of code | |||
621 | // that lives in the same file as its test. | |||
622 | } | |||
623 | ||||
276 | type Value struct{} | 624 | type Value struct{} | |
277 | 625 | |||
278 | // Method has no star on the receiver, | 626 | // Method has no star on the receiver, | |
279 | // for code coverage of TestNameChecker.loadDecl. | 627 | // for code coverage of TestNameChecker.loadDecl. | |
280 | func (Value) Method() {} | 628 | func (Value) Method() {} |
@@ -123,16 +123,16 @@ func NewOr(parts ...*Condition) *Conditi | @@ -123,16 +123,16 @@ func NewOr(parts ...*Condition) *Conditi | |||
123 | 123 | |||
124 | func toJSON(cond *Condition) string { | 124 | func toJSON(cond *Condition) string { | |
125 | jsonStr, _ := json.Marshal(cond) | 125 | jsonStr, _ := json.Marshal(cond) | |
126 | return strings.Replace(string(jsonStr), "\"", "", -1) | 126 | return strings.Replace(string(jsonStr), "\"", "", -1) | |
127 | } | 127 | } | |
128 | 128 | |||
129 | func Test(t *testing.T) { | 129 | func Test(t *testing.T) { | |
130 | check.Suite(new(Suite)) | 130 | check.Suite(new(Suite)) | |
131 | check.TestingT(t) | 131 | check.TestingT(t) | |
132 | } | 132 | } | |
133 | 133 | |||
134 | func (s *Suite) Test__test_names(c *check.C) { | 134 | func (s *Suite) Test__test_names(c *check.C) { | |
135 | ck := intqa.NewTestNameChecker(c.Errorf) | 135 | ck := intqa.NewTestNameChecker(c.Errorf) | |
136 | ck.Enable(intqa.EAll, -intqa.EMissingTest) | 136 | ck.Configure("*", "*", "*", -intqa.EMissingTest) | |
137 | ck.Check() | 137 | ck.Check() | |
138 | } | 138 | } |
@@ -85,16 +85,16 @@ func (s *Suite) Test_newVersion(c *check | @@ -85,16 +85,16 @@ func (s *Suite) Test_newVersion(c *check | |||
85 | &version{nil, 1}) | 85 | &version{nil, 1}) | |
86 | c.Check(newVersion("1.0.1a"), check.DeepEquals, | 86 | c.Check(newVersion("1.0.1a"), check.DeepEquals, | |
87 | &version{[]int{1, 0, 0, 0, 1, 1}, 0}) | 87 | &version{[]int{1, 0, 0, 0, 1, 1}, 0}) | |
88 | c.Check(newVersion("1.0.1z"), check.DeepEquals, | 88 | c.Check(newVersion("1.0.1z"), check.DeepEquals, | |
89 | &version{[]int{1, 0, 0, 0, 1, 26}, 0}) | 89 | &version{[]int{1, 0, 0, 0, 1, 26}, 0}) | |
90 | c.Check(newVersion("0pre20160620"), check.DeepEquals, | 90 | c.Check(newVersion("0pre20160620"), check.DeepEquals, | |
91 | &version{[]int{0, -1, 20160620}, 0}) | 91 | &version{[]int{0, -1, 20160620}, 0}) | |
92 | c.Check(newVersion("3.5.DEV1710"), check.DeepEquals, | 92 | c.Check(newVersion("3.5.DEV1710"), check.DeepEquals, | |
93 | &version{[]int{3, 0, 5, 0, 4, 5, 22, 1710}, 0}) | 93 | &version{[]int{3, 0, 5, 0, 4, 5, 22, 1710}, 0}) | |
94 | } | 94 | } | |
95 | 95 | |||
96 | func (s *Suite) Test__test_names(c *check.C) { | 96 | func (s *Suite) Test__test_names(c *check.C) { | |
97 | ck := intqa.NewTestNameChecker(c.Errorf) | 97 | ck := intqa.NewTestNameChecker(c.Errorf) | |
98 | ck.Enable(intqa.EAll, -intqa.EMissingTest) | 98 | ck.Configure("*", "*", "*", -intqa.EMissingTest) | |
99 | ck.Check() | 99 | ck.Check() | |
100 | } | 100 | } |
@@ -405,16 +405,16 @@ func (s *Suite) Test__Alpha(c *check.C) | @@ -405,16 +405,16 @@ func (s *Suite) Test__Alpha(c *check.C) | |||
405 | set := Alpha | 405 | set := Alpha | |
406 | 406 | |||
407 | c.Check(set.Contains(0x00), equals, false) | 407 | c.Check(set.Contains(0x00), equals, false) | |
408 | c.Check(set.Contains('@'), equals, false) | 408 | c.Check(set.Contains('@'), equals, false) | |
409 | c.Check(set.Contains('A'), equals, true) | 409 | c.Check(set.Contains('A'), equals, true) | |
410 | c.Check(set.Contains('Z'), equals, true) | 410 | c.Check(set.Contains('Z'), equals, true) | |
411 | c.Check(set.Contains('`'), equals, false) | 411 | c.Check(set.Contains('`'), equals, false) | |
412 | c.Check(set.Contains('a'), equals, true) | 412 | c.Check(set.Contains('a'), equals, true) | |
413 | c.Check(set.Contains('z'), equals, true) | 413 | c.Check(set.Contains('z'), equals, true) | |
414 | } | 414 | } | |
415 | 415 | |||
416 | func (s *Suite) Test__test_names(c *check.C) { | 416 | func (s *Suite) Test__test_names(c *check.C) { | |
417 | ck := intqa.NewTestNameChecker(c.Errorf) | 417 | ck := intqa.NewTestNameChecker(c.Errorf) | |
418 | ck.Enable(intqa.EAll, -intqa.EMissingTest) | 418 | ck.Configure("*", "*", "*", -intqa.EMissingTest) | |
419 | ck.Check() | 419 | ck.Check() | |
420 | } | 420 | } |
@@ -135,16 +135,16 @@ func (s *Suite) captureTracingOutput(act | @@ -135,16 +135,16 @@ func (s *Suite) captureTracingOutput(act | |||
135 | tracer.Tracing = false | 135 | tracer.Tracing = false | |
136 | tracer.Out = nil | 136 | tracer.Out = nil | |
137 | return out.String() | 137 | return out.String() | |
138 | } | 138 | } | |
139 | 139 | |||
140 | type str struct{} | 140 | type str struct{} | |
141 | 141 | |||
142 | func (str) String() string { | 142 | func (str) String() string { | |
143 | return "It's a string" | 143 | return "It's a string" | |
144 | } | 144 | } | |
145 | 145 | |||
146 | func (s *Suite) Test__test_names(c *check.C) { | 146 | func (s *Suite) Test__test_names(c *check.C) { | |
147 | ck := intqa.NewTestNameChecker(c.Errorf) | 147 | ck := intqa.NewTestNameChecker(c.Errorf) | |
148 | ck.Enable(intqa.EAll, -intqa.EMissingTest) | 148 | ck.Configure("*", "*", "*", -intqa.EMissingTest) | |
149 | ck.Check() | 149 | ck.Check() | |
150 | } | 150 | } |