pkgtools/pkglint: update to 20.1.18 Changes since 20.1.17: Fixed the algorithm for checking whether two patterns overlap, such as MACHINE_PLATFORM:MNetBSD-[0-9].*-*. Fixed wrong warning about foreign variable in SUBST block, as seen in geography/qgis.diff -r1.657 -r1.658 pkgsrc/pkgtools/pkglint/Makefile
(rillig)
@@ -1,17 +1,16 @@ | @@ -1,17 +1,16 @@ | |||
1 | # $NetBSD: Makefile,v 1.657 2020/06/17 09:54:12 bsiegert Exp $ | 1 | # $NetBSD: Makefile,v 1.658 2020/06/20 07:00:44 rillig Exp $ | |
2 | 2 | |||
3 | PKGNAME= pkglint-20.1.17 | 3 | PKGNAME= pkglint-20.1.18 | |
4 | PKGREVISION= 1 | |||
5 | CATEGORIES= pkgtools | 4 | CATEGORIES= pkgtools | |
6 | DISTNAME= tools | 5 | DISTNAME= tools | |
7 | MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} | 6 | MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} | |
8 | GITHUB_PROJECT= tools | 7 | GITHUB_PROJECT= tools | |
9 | GITHUB_TAG= 92d8274bd7b8a4c65f24bafe401a029e58392704 | 8 | GITHUB_TAG= 92d8274bd7b8a4c65f24bafe401a029e58392704 | |
10 | 9 | |||
11 | MAINTAINER= rillig@NetBSD.org | 10 | MAINTAINER= rillig@NetBSD.org | |
12 | HOMEPAGE= https://github.com/rillig/pkglint | 11 | HOMEPAGE= https://github.com/rillig/pkglint | |
13 | COMMENT= Verifier for NetBSD packages | 12 | COMMENT= Verifier for NetBSD packages | |
14 | LICENSE= 2-clause-bsd | 13 | LICENSE= 2-clause-bsd | |
15 | CONFLICTS+= pkglint4-[0-9]* | 14 | CONFLICTS+= pkglint4-[0-9]* | |
16 | 15 | |||
17 | USE_TOOLS+= pax | 16 | USE_TOOLS+= pax |
@@ -118,27 +118,26 @@ func Test__qa(t *testing.T) { | @@ -118,27 +118,26 @@ func Test__qa(t *testing.T) { | |||
118 | ck.Configure("mkparser.go", "*", "*", -intqa.EMissingTest) // TODO | 118 | ck.Configure("mkparser.go", "*", "*", -intqa.EMissingTest) // TODO | |
119 | ck.Configure("mkshparser.go", "*", "*", -intqa.EMissingTest) // TODO | 119 | ck.Configure("mkshparser.go", "*", "*", -intqa.EMissingTest) // TODO | |
120 | ck.Configure("mkshtypes.go", "*", "*", -intqa.EMissingTest) // TODO | 120 | ck.Configure("mkshtypes.go", "*", "*", -intqa.EMissingTest) // TODO | |
121 | ck.Configure("mkshwalker.go", "*", "*", -intqa.EMissingTest) // TODO | 121 | ck.Configure("mkshwalker.go", "*", "*", -intqa.EMissingTest) // TODO | |
122 | ck.Configure("mktokenslexer.go", "*", "*", -intqa.EMissingTest) // TODO | 122 | ck.Configure("mktokenslexer.go", "*", "*", -intqa.EMissingTest) // TODO | |
123 | ck.Configure("mktypes.go", "*", "*", -intqa.EMissingTest) // TODO | 123 | ck.Configure("mktypes.go", "*", "*", -intqa.EMissingTest) // TODO | |
124 | ck.Configure("options.go", "*", "*", -intqa.EMissingTest) // TODO | 124 | ck.Configure("options.go", "*", "*", -intqa.EMissingTest) // TODO | |
125 | ck.Configure("package.go", "*", "*", -intqa.EMissingTest) // TODO | 125 | ck.Configure("package.go", "*", "*", -intqa.EMissingTest) // TODO | |
126 | ck.Configure("paragraph.go", "*", "*", -intqa.EMissingTest) // TODO | 126 | ck.Configure("paragraph.go", "*", "*", -intqa.EMissingTest) // TODO | |
127 | ck.Configure("patches.go", "*", "*", -intqa.EMissingTest) // TODO | 127 | ck.Configure("patches.go", "*", "*", -intqa.EMissingTest) // TODO | |
128 | ck.Configure("pkglint.go", "*", "*", -intqa.EMissingTest) // TODO | 128 | ck.Configure("pkglint.go", "*", "*", -intqa.EMissingTest) // TODO | |
129 | ck.Configure("pkgsrc.go", "*", "*", -intqa.EMissingTest) // TODO | 129 | ck.Configure("pkgsrc.go", "*", "*", -intqa.EMissingTest) // TODO | |
130 | ck.Configure("redundantscope.go", "*", "*", -intqa.EMissingTest) // TODO | 130 | ck.Configure("redundantscope.go", "*", "*", -intqa.EMissingTest) // TODO | |
131 | ck.Configure("scope.go", "*", "*", -intqa.EMissingTest) // TODO | |||
132 | ck.Configure("shell.go", "*", "*", -intqa.EMissingTest) // TODO | 131 | ck.Configure("shell.go", "*", "*", -intqa.EMissingTest) // TODO | |
133 | ck.Configure("shtokenizer.go", "*", "*", -intqa.EMissingTest) // TODO | 132 | ck.Configure("shtokenizer.go", "*", "*", -intqa.EMissingTest) // TODO | |
134 | ck.Configure("shtypes.go", "*", "*", -intqa.EMissingTest) // TODO | 133 | ck.Configure("shtypes.go", "*", "*", -intqa.EMissingTest) // TODO | |
135 | ck.Configure("substcontext.go", "*", "*", -intqa.EMissingTest) // TODO | 134 | ck.Configure("substcontext.go", "*", "*", -intqa.EMissingTest) // TODO | |
136 | ck.Configure("tools.go", "*", "*", -intqa.EMissingTest) // TODO | 135 | ck.Configure("tools.go", "*", "*", -intqa.EMissingTest) // TODO | |
137 | ck.Configure("util.go", "*", "*", -intqa.EMissingTest) // TODO | 136 | ck.Configure("util.go", "*", "*", -intqa.EMissingTest) // TODO | |
138 | ck.Configure("var.go", "*", "*", -intqa.EMissingTest) // TODO | 137 | ck.Configure("var.go", "*", "*", -intqa.EMissingTest) // TODO | |
139 | 138 | |||
140 | ck.Configure("varalignblock.go", "*", "*", -intqa.EMissingTest) // TODO | 139 | ck.Configure("varalignblock.go", "*", "*", -intqa.EMissingTest) // TODO | |
141 | ck.Configure("varalignblock.go", "varalignLine", "*", +intqa.EMissingTest) // TODO: remove as redundant | 140 | ck.Configure("varalignblock.go", "varalignLine", "*", +intqa.EMissingTest) // TODO: remove as redundant | |
142 | 141 | |||
143 | ck.Configure("vardefs.go", "*", "*", -intqa.EMissingTest) // TODO | 142 | ck.Configure("vardefs.go", "*", "*", -intqa.EMissingTest) // TODO | |
144 | ck.Configure("vargroups.go", "*", "*", -intqa.EMissingTest) // TODO | 143 | ck.Configure("vargroups.go", "*", "*", -intqa.EMissingTest) // TODO |
@@ -104,33 +104,33 @@ func (s *Scope) def(name string, mkline | @@ -104,33 +104,33 @@ func (s *Scope) def(name string, mkline | |||
104 | case opAssignShell: | 104 | case opAssignShell: | |
105 | v.value = "" | 105 | v.value = "" | |
106 | v.indeterminate = true | 106 | v.indeterminate = true | |
107 | default: | 107 | default: | |
108 | v.value = mkline.Value() | 108 | v.value = mkline.Value() | |
109 | } | 109 | } | |
110 | } | 110 | } | |
111 | 111 | |||
112 | func (s *Scope) Fallback(varname string, value string) { | 112 | func (s *Scope) Fallback(varname string, value string) { | |
113 | s.create(varname).fallback = value | 113 | s.create(varname).fallback = value | |
114 | } | 114 | } | |
115 | 115 | |||
116 | // Use marks the variable and its canonicalized form as used. | 116 | // Use marks the variable and its canonicalized form as used. | |
117 | func (s *Scope) Use(varname string, line *MkLine, time VucTime) { | 117 | func (s *Scope) Use(varname string, mkline *MkLine, time VucTime) { | |
118 | use := func(name string) { | 118 | use := func(name string) { | |
119 | v := s.create(name) | 119 | v := s.create(name) | |
120 | if v.used == nil { | 120 | if v.used == nil { | |
121 | v.used = line | 121 | v.used = mkline | |
122 | if trace.Tracing { | 122 | if trace.Tracing { | |
123 | trace.Step2("Using %q in %s", name, line.String()) | 123 | trace.Step2("Using %q in %s", name, mkline.String()) | |
124 | } | 124 | } | |
125 | } | 125 | } | |
126 | if time == VucLoadTime { | 126 | if time == VucLoadTime { | |
127 | v.usedAtLoadTime = true | 127 | v.usedAtLoadTime = true | |
128 | } | 128 | } | |
129 | } | 129 | } | |
130 | 130 | |||
131 | use(varname) | 131 | use(varname) | |
132 | use(varnameCanon(varname)) | 132 | use(varnameCanon(varname)) | |
133 | } | 133 | } | |
134 | 134 | |||
135 | // Mentioned returns the first line in which the variable is either: | 135 | // Mentioned returns the first line in which the variable is either: | |
136 | // - defined, | 136 | // - defined, |
@@ -24,26 +24,63 @@ func (s *Suite) Test_Scope__commented_va | @@ -24,26 +24,63 @@ func (s *Suite) Test_Scope__commented_va | |||
24 | t.CheckEquals(scope.IsDefined("VAR"), false) | 24 | t.CheckEquals(scope.IsDefined("VAR"), false) | |
25 | t.Check(scope.FirstDefinition("VAR"), check.IsNil) | 25 | t.Check(scope.FirstDefinition("VAR"), check.IsNil) | |
26 | t.Check(scope.LastDefinition("VAR"), check.IsNil) | 26 | t.Check(scope.LastDefinition("VAR"), check.IsNil) | |
27 | 27 | |||
28 | t.CheckEquals(scope.Mentioned("VAR"), mkline) | 28 | t.CheckEquals(scope.Mentioned("VAR"), mkline) | |
29 | t.CheckEquals(scope.Commented("VAR"), mkline) | 29 | t.CheckEquals(scope.Commented("VAR"), mkline) | |
30 | 30 | |||
31 | value, found, indeterminate := scope.LastValueFound("VAR") | 31 | value, found, indeterminate := scope.LastValueFound("VAR") | |
32 | t.CheckEquals(value, "") | 32 | t.CheckEquals(value, "") | |
33 | t.CheckEquals(found, false) | 33 | t.CheckEquals(found, false) | |
34 | t.CheckEquals(indeterminate, false) | 34 | t.CheckEquals(indeterminate, false) | |
35 | } | 35 | } | |
36 | 36 | |||
37 | func (s *Suite) Test_NewScope(c *check.C) { | |||
38 | t := s.Init(c) | |||
39 | ||||
40 | scope := NewScope() | |||
41 | ||||
42 | t.Check(scope.names, check.IsNil) | |||
43 | t.Check(scope.vs, check.NotNil) | |||
44 | t.Check(scope.vs, check.HasLen, 0) | |||
45 | } | |||
46 | ||||
47 | func (s *Suite) Test_Scope_varnames(c *check.C) { | |||
48 | t := s.Init(c) | |||
49 | ||||
50 | scope := NewScope() | |||
51 | mkline := t.NewMkLine("filename.mk", 3, "DEFINED=\t${USED}") | |||
52 | ||||
53 | t.Check(scope.varnames(), check.IsNil) | |||
54 | ||||
55 | scope.Define("DEFINED", mkline) | |||
56 | scope.Use("USED", mkline, VucRunTime) | |||
57 | ||||
58 | t.CheckDeepEquals(scope.varnames(), []string{"DEFINED", "USED"}) | |||
59 | scope.varnames()[0] = "modified" // just to demonstrate the caching | |||
60 | t.CheckDeepEquals(scope.varnames(), []string{"modified", "USED"}) | |||
61 | } | |||
62 | ||||
63 | func (s *Suite) Test_Scope_create(c *check.C) { | |||
64 | t := s.Init(c) | |||
65 | ||||
66 | scope := NewScope() | |||
67 | ||||
68 | v1 := scope.create("VAR") | |||
69 | v2 := scope.create("VAR") | |||
70 | ||||
71 | t.CheckEquals(v1, v2) | |||
72 | } | |||
73 | ||||
37 | func (s *Suite) Test_Scope_Define(c *check.C) { | 74 | func (s *Suite) Test_Scope_Define(c *check.C) { | |
38 | t := s.Init(c) | 75 | t := s.Init(c) | |
39 | 76 | |||
40 | scope := NewScope() | 77 | scope := NewScope() | |
41 | 78 | |||
42 | test := func(line string, found, indeterminate bool, value string) { | 79 | test := func(line string, found, indeterminate bool, value string) { | |
43 | mkline := t.NewMkLine("file.mk", 123, line) | 80 | mkline := t.NewMkLine("file.mk", 123, line) | |
44 | scope.Define("BUILD_DIRS", mkline) | 81 | scope.Define("BUILD_DIRS", mkline) | |
45 | 82 | |||
46 | actualValue, actualFound, actualIndeterminate := scope.LastValueFound("BUILD_DIRS") | 83 | actualValue, actualFound, actualIndeterminate := scope.LastValueFound("BUILD_DIRS") | |
47 | 84 | |||
48 | t.CheckDeepEquals( | 85 | t.CheckDeepEquals( | |
49 | []interface{}{actualFound, actualIndeterminate, actualValue}, | 86 | []interface{}{actualFound, actualIndeterminate, actualValue}, | |
@@ -65,26 +102,65 @@ func (s *Suite) Test_Scope_Define(c *che | @@ -65,26 +102,65 @@ func (s *Suite) Test_Scope_Define(c *che | |||
65 | // Later default assignments do not have an effect. | 102 | // Later default assignments do not have an effect. | |
66 | test("BUILD_DIRS?=\tdefault", | 103 | test("BUILD_DIRS?=\tdefault", | |
67 | true, false, "one two three four") | 104 | true, false, "one two three four") | |
68 | 105 | |||
69 | test("BUILD_DIRS!=\techo dynamic", | 106 | test("BUILD_DIRS!=\techo dynamic", | |
70 | true, true, "") | 107 | true, true, "") | |
71 | 108 | |||
72 | // The shell assignment above sets the variable to an indeterminate | 109 | // The shell assignment above sets the variable to an indeterminate | |
73 | // value, after which all further default assignments are ignored. | 110 | // value, after which all further default assignments are ignored. | |
74 | test("BUILD_DIRS?=\tdefault after shell assignment", | 111 | test("BUILD_DIRS?=\tdefault after shell assignment", | |
75 | true, true, "") | 112 | true, true, "") | |
76 | } | 113 | } | |
77 | 114 | |||
115 | func (s *Suite) Test_Scope_def(c *check.C) { | |||
116 | t := s.Init(c) | |||
117 | ||||
118 | mkline := t.NewMkLine("filename.mk", 3, "VAR=\tvalue") | |||
119 | scope := NewScope() | |||
120 | ||||
121 | scope.def("VAR.param", mkline) | |||
122 | ||||
123 | t.Check(scope.FirstDefinition("VAR"), check.IsNil) | |||
124 | t.CheckEquals(scope.FirstDefinition("VAR.param"), mkline) | |||
125 | t.Check(scope.FirstDefinition("VAR.*"), check.IsNil) | |||
126 | } | |||
127 | ||||
128 | func (s *Suite) Test_Scope_Fallback(c *check.C) { | |||
129 | t := s.Init(c) | |||
130 | ||||
131 | mkline := t.NewMkLine("filename.mk", 3, "VAR=\tvalue") | |||
132 | scope := NewScope() | |||
133 | scope.def("VAR.param", mkline) | |||
134 | ||||
135 | scope.Fallback("FALLBACK", "fallback") | |||
136 | ||||
137 | t.CheckEquals(scope.LastValue("VAR.param"), "value") | |||
138 | t.CheckEquals(scope.LastValue("FALLBACK"), "fallback") | |||
139 | t.CheckEquals(scope.LastValue("UNDEFINED"), "") | |||
140 | } | |||
141 | ||||
142 | func (s *Suite) Test_Scope_Use(c *check.C) { | |||
143 | t := s.Init(c) | |||
144 | ||||
145 | mkline := t.NewMkLine("filename.mk", 3, "VAR=\t${USED}") | |||
146 | scope := NewScope() | |||
147 | scope.Define("VAR", mkline) | |||
148 | scope.Use("USED", mkline, VucRunTime) | |||
149 | ||||
150 | t.CheckEquals(scope.LastValue("VAR"), "${USED}") | |||
151 | t.CheckEquals(scope.LastValue("USED"), "") | |||
152 | } | |||
153 | ||||
78 | func (s *Suite) Test_Scope_Mentioned(c *check.C) { | 154 | func (s *Suite) Test_Scope_Mentioned(c *check.C) { | |
79 | t := s.Init(c) | 155 | t := s.Init(c) | |
80 | 156 | |||
81 | assigned := t.NewMkLine("filename.mk", 3, "VAR=\tvalue") | 157 | assigned := t.NewMkLine("filename.mk", 3, "VAR=\tvalue") | |
82 | commented := t.NewMkLine("filename.mk", 4, "#COMMENTED=\tvalue") | 158 | commented := t.NewMkLine("filename.mk", 4, "#COMMENTED=\tvalue") | |
83 | documented := t.NewMkLine("filename.mk", 5, "# DOCUMENTED is a variable.") | 159 | documented := t.NewMkLine("filename.mk", 5, "# DOCUMENTED is a variable.") | |
84 | 160 | |||
85 | scope := NewScope() | 161 | scope := NewScope() | |
86 | scope.Define("VAR", assigned) | 162 | scope.Define("VAR", assigned) | |
87 | scope.Define("COMMENTED", commented) | 163 | scope.Define("COMMENTED", commented) | |
88 | scope.Define("DOCUMENTED", documented) | 164 | scope.Define("DOCUMENTED", documented) | |
89 | 165 | |||
90 | t.CheckEquals(scope.Mentioned("VAR"), assigned) | 166 | t.CheckEquals(scope.Mentioned("VAR"), assigned) | |
@@ -92,87 +168,159 @@ func (s *Suite) Test_Scope_Mentioned(c * | @@ -92,87 +168,159 @@ func (s *Suite) Test_Scope_Mentioned(c * | |||
92 | t.CheckEquals(scope.Mentioned("DOCUMENTED"), documented) | 168 | t.CheckEquals(scope.Mentioned("DOCUMENTED"), documented) | |
93 | t.Check(scope.Mentioned("UNKNOWN"), check.IsNil) | 169 | t.Check(scope.Mentioned("UNKNOWN"), check.IsNil) | |
94 | } | 170 | } | |
95 | 171 | |||
96 | func (s *Suite) Test_Scope_IsDefined(c *check.C) { | 172 | func (s *Suite) Test_Scope_IsDefined(c *check.C) { | |
97 | t := s.Init(c) | 173 | t := s.Init(c) | |
98 | 174 | |||
99 | scope := NewScope() | 175 | scope := NewScope() | |
100 | scope.Define("VAR.param", t.NewMkLine("file.mk", 1, "VAR.param=value")) | 176 | scope.Define("VAR.param", t.NewMkLine("file.mk", 1, "VAR.param=value")) | |
101 | 177 | |||
102 | t.CheckEquals(scope.IsDefined("VAR.param"), true) | 178 | t.CheckEquals(scope.IsDefined("VAR.param"), true) | |
103 | t.CheckEquals(scope.IsDefined("VAR.other"), false) | 179 | t.CheckEquals(scope.IsDefined("VAR.other"), false) | |
104 | t.CheckEquals(scope.IsDefined("VARIABLE.*"), false) | 180 | t.CheckEquals(scope.IsDefined("VARIABLE.*"), false) | |
181 | } | |||
182 | ||||
183 | func (s *Suite) Test_Scope_IsDefinedSimilar(c *check.C) { | |||
184 | t := s.Init(c) | |||
185 | ||||
186 | scope := NewScope() | |||
187 | scope.Define("VAR.param", t.NewMkLine("file.mk", 1, "VAR.param=value")) | |||
105 | 188 | |||
106 | t.CheckEquals(scope.IsDefinedSimilar("VAR.param"), true) | 189 | t.CheckEquals(scope.IsDefinedSimilar("VAR.param"), true) | |
107 | t.CheckEquals(scope.IsDefinedSimilar("VAR.other"), true) | 190 | t.CheckEquals(scope.IsDefinedSimilar("VAR.other"), true) | |
108 | t.CheckEquals(scope.IsDefinedSimilar("VARIABLE.*"), false) | 191 | t.CheckEquals(scope.IsDefinedSimilar("VARIABLE.*"), false) | |
109 | } | 192 | } | |
110 | 193 | |||
111 | func (s *Suite) Test_Scope_IsUsed(c *check.C) { | 194 | func (s *Suite) Test_Scope_IsUsed(c *check.C) { | |
112 | t := s.Init(c) | 195 | t := s.Init(c) | |
113 | 196 | |||
114 | scope := NewScope() | 197 | scope := NewScope() | |
115 | mkline := t.NewMkLine("file.mk", 1, "\techo ${VAR.param}") | 198 | mkline := t.NewMkLine("file.mk", 1, "\techo ${VAR.param}") | |
116 | scope.Use("VAR.param", mkline, VucRunTime) | 199 | scope.Use("VAR.param", mkline, VucRunTime) | |
117 | 200 | |||
118 | t.CheckEquals(scope.IsUsed("VAR.param"), true) | 201 | t.CheckEquals(scope.IsUsed("VAR.param"), true) | |
119 | t.CheckEquals(scope.IsUsed("VAR.other"), false) | 202 | t.CheckEquals(scope.IsUsed("VAR.other"), false) | |
120 | t.CheckEquals(scope.IsUsed("VARIABLE.*"), false) | 203 | t.CheckEquals(scope.IsUsed("VARIABLE.*"), false) | |
204 | } | |||
205 | ||||
206 | func (s *Suite) Test_Scope_IsUsedSimilar(c *check.C) { | |||
207 | t := s.Init(c) | |||
208 | ||||
209 | scope := NewScope() | |||
210 | mkline := t.NewMkLine("file.mk", 1, "\techo ${VAR.param}") | |||
211 | scope.Use("VAR.param", mkline, VucRunTime) | |||
121 | 212 | |||
122 | t.CheckEquals(scope.IsUsedSimilar("VAR.param"), true) | 213 | t.CheckEquals(scope.IsUsedSimilar("VAR.param"), true) | |
123 | t.CheckEquals(scope.IsUsedSimilar("VAR.other"), true) | 214 | t.CheckEquals(scope.IsUsedSimilar("VAR.other"), true) | |
124 | t.CheckEquals(scope.IsUsedSimilar("VARIABLE.*"), false) | 215 | t.CheckEquals(scope.IsUsedSimilar("VARIABLE.*"), false) | |
125 | } | 216 | } | |
126 | 217 | |||
218 | func (s *Suite) Test_Scope_IsUsedAtLoadTime(c *check.C) { | |||
219 | t := s.Init(c) | |||
220 | ||||
221 | scope := NewScope() | |||
222 | mkline := t.NewMkLine("file.mk", 1, "\techo ${VAR.param}") | |||
223 | ||||
224 | scope.Use("LOAD_TIME", mkline, VucLoadTime) | |||
225 | scope.Use("RUN_TIME", mkline, VucRunTime) | |||
226 | ||||
227 | t.CheckEquals(scope.IsUsedAtLoadTime("LOAD_TIME"), true) | |||
228 | t.CheckEquals(scope.IsUsedAtLoadTime("RUN_TIME"), false) | |||
229 | t.CheckEquals(scope.IsUsedAtLoadTime("UNDEFINED"), false) | |||
230 | } | |||
231 | ||||
127 | func (s *Suite) Test_Scope_FirstDefinition(c *check.C) { | 232 | func (s *Suite) Test_Scope_FirstDefinition(c *check.C) { | |
128 | t := s.Init(c) | 233 | t := s.Init(c) | |
129 | 234 | |||
130 | mkline1 := t.NewMkLine("fname.mk", 3, "VAR=\tvalue") | 235 | mkline3 := t.NewMkLine("fname.mk", 3, "VAR=\tvalue") | |
131 | mkline2 := t.NewMkLine("fname.mk", 3, ".if ${SNEAKY::=value}") | 236 | mkline4 := t.NewMkLine("fname.mk", 4, ".if ${SNEAKY::=value}") | |
237 | mkline5 := t.NewMkLine("fname.mk", 5, ".if ${USED}") | |||
132 | 238 | |||
133 | scope := NewScope() | 239 | scope := NewScope() | |
134 | scope.Define("VAR", mkline1) | 240 | scope.Define("VAR", mkline3) | |
135 | scope.Define("SNEAKY", mkline2) | 241 | scope.Define("SNEAKY", mkline4) | |
242 | scope.Use("USED", mkline5, VucLoadTime) | |||
136 | 243 | |||
137 | t.CheckEquals(scope.FirstDefinition("VAR"), mkline1) | 244 | t.CheckEquals(scope.FirstDefinition("VAR"), mkline3) | |
138 | 245 | |||
139 | // This call returns nil because it's not a variable assignment | 246 | // This call returns nil because it's not a variable assignment | |
140 | // and the calling code typically assumes a variable definition. | 247 | // and the calling code typically assumes a variable definition. | |
141 | // These sneaky variables with implicit definition are an edge | 248 | // These sneaky variables with implicit definition are an edge | |
142 | // case that only few people actually know. It's better that way. | 249 | // case that only few people actually know. It's better that way. | |
143 | t.Check(scope.FirstDefinition("SNEAKY"), check.IsNil) | 250 | t.Check(scope.FirstDefinition("SNEAKY"), check.IsNil) | |
144 | 251 | |||
252 | t.Check(scope.FirstDefinition("USED"), check.IsNil) | |||
253 | ||||
145 | t.CheckOutputLines( | 254 | t.CheckOutputLines( | |
146 | "ERROR: fname.mk:3: Assignment modifiers like \":=\" " + | 255 | "ERROR: fname.mk:4: Assignment modifiers like \":=\" " + | |
147 | "must not be used at all.") | 256 | "must not be used at all.") | |
148 | } | 257 | } | |
149 | 258 | |||
259 | func (s *Suite) Test_Scope_LastDefinition(c *check.C) { | |||
260 | t := s.Init(c) | |||
261 | ||||
262 | mkline3 := t.NewMkLine("fname.mk", 3, "VAR=\tfirst") | |||
263 | mkline4 := t.NewMkLine("fname.mk", 4, "VAR=\t${USED}") | |||
264 | ||||
265 | scope := NewScope() | |||
266 | scope.Define("VAR", mkline3) | |||
267 | scope.Define("VAR", mkline4) | |||
268 | scope.Use("USED", mkline4, VucRunTime) | |||
269 | ||||
270 | t.CheckEquals(scope.LastDefinition("VAR"), mkline4) | |||
271 | t.Check(scope.LastDefinition("UNDEFINED"), check.IsNil) | |||
272 | t.Check(scope.LastDefinition("USED"), check.IsNil) | |||
273 | } | |||
274 | ||||
150 | func (s *Suite) Test_Scope_Commented(c *check.C) { | 275 | func (s *Suite) Test_Scope_Commented(c *check.C) { | |
151 | t := s.Init(c) | 276 | t := s.Init(c) | |
152 | 277 | |||
153 | assigned := t.NewMkLine("filename.mk", 3, "VAR=\tvalue") | 278 | assigned := t.NewMkLine("filename.mk", 3, "VAR=\tvalue") | |
154 | commented := t.NewMkLine("filename.mk", 4, "#COMMENTED=\tvalue") | 279 | commented := t.NewMkLine("filename.mk", 4, "#COMMENTED=\tvalue") | |
155 | documented := t.NewMkLine("filename.mk", 5, "# DOCUMENTED is a variable.") | 280 | documented := t.NewMkLine("filename.mk", 5, "# DOCUMENTED is a variable.") | |
281 | used := t.NewMkLine("filename.mk", 6, "ANY=\t${USED}") | |||
156 | 282 | |||
157 | scope := NewScope() | 283 | scope := NewScope() | |
158 | scope.Define("VAR", assigned) | 284 | scope.Define("VAR", assigned) | |
159 | scope.Define("COMMENTED", commented) | 285 | scope.Define("COMMENTED", commented) | |
160 | scope.Define("DOCUMENTED", documented) | 286 | scope.Define("DOCUMENTED", documented) | |
287 | scope.Use("USED", used, VucRunTime) | |||
161 | 288 | |||
162 | t.Check(scope.Commented("VAR"), check.IsNil) | 289 | t.Check(scope.Commented("VAR"), check.IsNil) | |
163 | t.CheckEquals(scope.Commented("COMMENTED"), commented) | 290 | t.CheckEquals(scope.Commented("COMMENTED"), commented) | |
164 | t.Check(scope.Commented("DOCUMENTED"), check.IsNil) | 291 | t.Check(scope.Commented("DOCUMENTED"), check.IsNil) | |
165 | t.Check(scope.Commented("UNKNOWN"), check.IsNil) | 292 | t.Check(scope.Commented("UNKNOWN"), check.IsNil) | |
293 | t.Check(scope.Commented("USED"), check.IsNil) | |||
294 | } | |||
295 | ||||
296 | func (s *Suite) Test_Scope_FirstUse(c *check.C) { | |||
297 | t := s.Init(c) | |||
298 | ||||
299 | mklines := t.NewMkLines("file.mk", | |||
300 | MkCvsID, | |||
301 | "VAR1=\t${USED}", | |||
302 | "VAR2=\t${USED}") | |||
303 | ||||
304 | mklines.Check() | |||
305 | ||||
306 | scope := mklines.allVars | |||
307 | t.CheckEquals(scope.FirstUse("USED"), mklines.mklines[1]) | |||
308 | t.Check(scope.FirstUse("UNUSED"), check.IsNil) | |||
309 | ||||
310 | t.CheckOutputLines( | |||
311 | "WARN: file.mk:2: VAR1 is defined but not used.", | |||
312 | "WARN: file.mk:2: USED is used but not defined.", | |||
313 | "WARN: file.mk:3: VAR2 is defined but not used.") | |||
166 | } | 314 | } | |
167 | 315 | |||
168 | func (s *Suite) Test_Scope_LastValue(c *check.C) { | 316 | func (s *Suite) Test_Scope_LastValue(c *check.C) { | |
169 | t := s.Init(c) | 317 | t := s.Init(c) | |
170 | 318 | |||
171 | mklines := t.NewMkLines("file.mk", | 319 | mklines := t.NewMkLines("file.mk", | |
172 | MkCvsID, | 320 | MkCvsID, | |
173 | "VAR=\tfirst", | 321 | "VAR=\tfirst", | |
174 | "VAR=\tsecond", | 322 | "VAR=\tsecond", | |
175 | ".if 1", | 323 | ".if 1", | |
176 | "VAR=\tthird (conditional)", | 324 | "VAR=\tthird (conditional)", | |
177 | ".endif") | 325 | ".endif") | |
178 | 326 | |||
@@ -200,28 +348,74 @@ func (s *Suite) Test_Scope_LastValue__ap | @@ -200,28 +348,74 @@ func (s *Suite) Test_Scope_LastValue__ap | |||
200 | "${PLIST.one}${PLIST.two}bin/program") | 348 | "${PLIST.one}${PLIST.two}bin/program") | |
201 | t.CreateFileLines("Makefile.common", | 349 | t.CreateFileLines("Makefile.common", | |
202 | MkCvsID, | 350 | MkCvsID, | |
203 | "PLIST_VARS=\tone", | 351 | "PLIST_VARS=\tone", | |
204 | "PLIST.one=\tyes") | 352 | "PLIST.one=\tyes") | |
205 | pkg := NewPackage(".") | 353 | pkg := NewPackage(".") | |
206 | t.FinishSetUp() | 354 | t.FinishSetUp() | |
207 | 355 | |||
208 | pkg.Check() | 356 | pkg.Check() | |
209 | 357 | |||
210 | t.CheckEquals(pkg.vars.LastValue("PLIST_VARS"), "one two") | 358 | t.CheckEquals(pkg.vars.LastValue("PLIST_VARS"), "one two") | |
211 | } | 359 | } | |
212 | 360 | |||
361 | func (s *Suite) Test_Scope_LastValueFound(c *check.C) { | |||
362 | t := s.Init(c) | |||
363 | ||||
364 | mklines := t.NewMkLines("file.mk", | |||
365 | MkCvsID, | |||
366 | "VAR=\tfirst", | |||
367 | "VAR=\tsecond", | |||
368 | ".if 1", | |||
369 | "VAR=\tthird (conditional)", | |||
370 | ".endif") | |||
371 | ||||
372 | mklines.Check() | |||
373 | ||||
374 | value, found, indeterminate := mklines.allVars.LastValueFound("VAR") | |||
375 | t.CheckEquals(value, "third (conditional)") | |||
376 | t.CheckEquals(found, true) | |||
377 | t.CheckEquals(indeterminate, false) // TODO: why? | |||
378 | ||||
379 | t.CheckOutputLines( | |||
380 | "WARN: file.mk:2: VAR is defined but not used.") | |||
381 | } | |||
382 | ||||
383 | // Scope.DefineAll copies only the variable definitions, | |||
384 | // but not the uses of variables. | |||
213 | func (s *Suite) Test_Scope_DefineAll(c *check.C) { | 385 | func (s *Suite) Test_Scope_DefineAll(c *check.C) { | |
214 | t := s.Init(c) | 386 | t := s.Init(c) | |
215 | 387 | |||
388 | mkline := t.NewMkLine("filename.mk", 123, "VAR=\t${USED}") | |||
389 | ||||
216 | src := NewScope() | 390 | src := NewScope() | |
391 | src.Use("USED", mkline, VucRunTime) | |||
217 | 392 | |||
218 | dst := NewScope() | 393 | dst := NewScope() | |
219 | dst.DefineAll(&src) | 394 | dst.DefineAll(&src) | |
220 | 395 | |||
221 | c.Check(dst.vs, check.HasLen, 0) | 396 | c.Check(dst.vs, check.HasLen, 0) | |
222 | 397 | |||
223 | src.Define("VAR", t.NewMkLine("file.mk", 1, "VAR=value")) | 398 | src.Define("VAR", t.NewMkLine("file.mk", 1, "VAR=value")) | |
224 | dst.DefineAll(&src) | 399 | dst.DefineAll(&src) | |
225 | 400 | |||
226 | t.CheckEquals(dst.IsDefined("VAR"), true) | 401 | t.CheckEquals(dst.IsDefined("VAR"), true) | |
227 | } | 402 | } | |
403 | ||||
404 | func (s *Suite) Test_Scope_forEach(c *check.C) { | |||
405 | t := s.Init(c) | |||
406 | ||||
407 | mkline := t.NewMkLine("filename.mk", 123, "VAR=\t${USED}") | |||
408 | ||||
409 | scope := NewScope() | |||
410 | scope.Define("VAR", mkline) | |||
411 | scope.Use("USED", mkline, VucRunTime) | |||
412 | ||||
413 | var result []string | |||
414 | scope.forEach(func(varname string, data *scopeVar) { | |||
415 | result = append(result, varname+"="+data.value) | |||
416 | }) | |||
417 | ||||
418 | t.CheckDeepEquals(result, []string{ | |||
419 | "USED=", | |||
420 | "VAR=${USED}"}) | |||
421 | } |
@@ -410,26 +410,28 @@ func (b *substBlock) varassign(mkline *M | @@ -410,26 +410,28 @@ func (b *substBlock) varassign(mkline *M | |||
410 | case "SUBST_STAGE.*": | 410 | case "SUBST_STAGE.*": | |
411 | b.varassignStage(mkline, pkg) | 411 | b.varassignStage(mkline, pkg) | |
412 | case "SUBST_MESSAGE.*": | 412 | case "SUBST_MESSAGE.*": | |
413 | b.varassignMessages(mkline) | 413 | b.varassignMessages(mkline) | |
414 | case "SUBST_FILES.*": | 414 | case "SUBST_FILES.*": | |
415 | b.varassignFiles(mkline) | 415 | b.varassignFiles(mkline) | |
416 | case "SUBST_SED.*": | 416 | case "SUBST_SED.*": | |
417 | b.varassignSed(mkline) | 417 | b.varassignSed(mkline) | |
418 | case "SUBST_VARS.*": | 418 | case "SUBST_VARS.*": | |
419 | b.varassignVars(mkline) | 419 | b.varassignVars(mkline) | |
420 | case "SUBST_FILTER_CMD.*": | 420 | case "SUBST_FILTER_CMD.*": | |
421 | b.varassignFilterCmd(mkline) | 421 | b.varassignFilterCmd(mkline) | |
422 | } | 422 | } | |
423 | ||||
424 | b.varassignAllowForeign(mkline) | |||
423 | } | 425 | } | |
424 | 426 | |||
425 | func (b *substBlock) varassignStage(mkline *MkLine, pkg *Package) { | 427 | func (b *substBlock) varassignStage(mkline *MkLine, pkg *Package) { | |
426 | if b.isConditional() { | 428 | if b.isConditional() { | |
427 | mkline.Warnf("%s should not be defined conditionally.", mkline.Varname()) | 429 | mkline.Warnf("%s should not be defined conditionally.", mkline.Varname()) | |
428 | } | 430 | } | |
429 | 431 | |||
430 | b.dupString(mkline, ssStage) | 432 | b.dupString(mkline, ssStage) | |
431 | 433 | |||
432 | value := mkline.Value() | 434 | value := mkline.Value() | |
433 | if value == "pre-patch" || value == "post-patch" { | 435 | if value == "pre-patch" || value == "post-patch" { | |
434 | fix := mkline.Autofix() | 436 | fix := mkline.Autofix() | |
435 | fix.Warnf("Substitutions should not happen in the patch phase.") | 437 | fix.Warnf("Substitutions should not happen in the patch phase.") | |
@@ -481,26 +483,32 @@ func (b *substBlock) varassignVars(mklin | @@ -481,26 +483,32 @@ func (b *substBlock) varassignVars(mklin | |||
481 | b.dupList(mkline, ssVars, ssVarsAutofix) | 483 | b.dupList(mkline, ssVars, ssVarsAutofix) | |
482 | b.addSeen(ssTransform) | 484 | b.addSeen(ssTransform) | |
483 | 485 | |||
484 | for _, substVar := range mkline.Fields() { | 486 | for _, substVar := range mkline.Fields() { | |
485 | b.allowVar(substVar) | 487 | b.allowVar(substVar) | |
486 | } | 488 | } | |
487 | } | 489 | } | |
488 | 490 | |||
489 | func (b *substBlock) varassignFilterCmd(mkline *MkLine) { | 491 | func (b *substBlock) varassignFilterCmd(mkline *MkLine) { | |
490 | b.dupString(mkline, ssFilterCmd) | 492 | b.dupString(mkline, ssFilterCmd) | |
491 | b.addSeen(ssTransform) | 493 | b.addSeen(ssTransform) | |
492 | } | 494 | } | |
493 | 495 | |||
496 | func (b *substBlock) varassignAllowForeign(mkline *MkLine) { | |||
497 | mkline.ForEachUsed(func(varUse *MkVarUse, time VucTime) { | |||
498 | b.allowVar(varUse.varname) | |||
499 | }) | |||
500 | } | |||
501 | ||||
494 | func (b *substBlock) suggestSubstVars(mkline *MkLine) { | 502 | func (b *substBlock) suggestSubstVars(mkline *MkLine) { | |
495 | 503 | |||
496 | tokens, _ := splitIntoShellTokens(mkline.Line, mkline.Value()) | 504 | tokens, _ := splitIntoShellTokens(mkline.Line, mkline.Value()) | |
497 | for _, token := range tokens { | 505 | for _, token := range tokens { | |
498 | varname := b.extractVarname(mkline.UnquoteShell(token, false)) | 506 | varname := b.extractVarname(mkline.UnquoteShell(token, false)) | |
499 | if varname == "" { | 507 | if varname == "" { | |
500 | continue | 508 | continue | |
501 | } | 509 | } | |
502 | 510 | |||
503 | id := b.id | 511 | id := b.id | |
504 | varop := sprintf("SUBST_VARS.%s%s%s", | 512 | varop := sprintf("SUBST_VARS.%s%s%s", | |
505 | id, | 513 | id, | |
506 | condStr(hasSuffix(id, "+"), " ", ""), | 514 | condStr(hasSuffix(id, "+"), " ", ""), |
@@ -968,26 +968,42 @@ func (s *Suite) Test_substScope_finish__ | @@ -968,26 +968,42 @@ func (s *Suite) Test_substScope_finish__ | |||
968 | "VAR2= value2", | 968 | "VAR2= value2", | |
969 | "SUBST_FILES.1= files", | 969 | "SUBST_FILES.1= files", | |
970 | "SUBST_VARS.1= VAR1", | 970 | "SUBST_VARS.1= VAR1", | |
971 | "SUBST_STAGE.2= pre-configure", | 971 | "SUBST_STAGE.2= pre-configure", | |
972 | "SUBST_FILES.2= files", | 972 | "SUBST_FILES.2= files", | |
973 | "VAR1= value1", | 973 | "VAR1= value1", | |
974 | "SUBST_VARS.2= VAR2") | 974 | "SUBST_VARS.2= VAR2") | |
975 | 975 | |||
976 | t.CheckOutputLines( | 976 | t.CheckOutputLines( | |
977 | "NOTE: filename.mk:1: " + | 977 | "NOTE: filename.mk:1: " + | |
978 | "Please add only one class at a time to SUBST_CLASSES.") | 978 | "Please add only one class at a time to SUBST_CLASSES.") | |
979 | } | 979 | } | |
980 | 980 | |||
981 | func (s *Suite) Test_substScope_finish__indirect_SUBST_FILES(c *check.C) { | |||
982 | t := s.Init(c) | |||
983 | ||||
984 | t.RunSubst( | |||
985 | "SUBST_CLASSES+= 1", | |||
986 | "SUBST_STAGE.1= pre-configure", | |||
987 | "S1_CMD= echo file", | |||
988 | "SUBST_FILES.1= ${S1_CMD:sh}", | |||
989 | "SUBST_VARS.1= PREFIX") | |||
990 | ||||
991 | // Since S1_CMD is used in SUBST_FILES, it is not foreign | |||
992 | // but obviously belongs to the SUBST block. | |||
993 | // Before 2020-06-20, pkglint had warned about this. | |||
994 | t.CheckOutputEmpty() | |||
995 | } | |||
996 | ||||
981 | func (s *Suite) Test_substScope_prepareSubstClasses(c *check.C) { | 997 | func (s *Suite) Test_substScope_prepareSubstClasses(c *check.C) { | |
982 | t := s.Init(c) | 998 | t := s.Init(c) | |
983 | 999 | |||
984 | t.RunSubst( | 1000 | t.RunSubst( | |
985 | "SUBST_CLASSES+= 1", | 1001 | "SUBST_CLASSES+= 1", | |
986 | "SUBST_STAGE.1= post-configure", | 1002 | "SUBST_STAGE.1= post-configure", | |
987 | ".if 0") | 1003 | ".if 0") | |
988 | 1004 | |||
989 | // There's no need to warn about unbalanced conditionals | 1005 | // There's no need to warn about unbalanced conditionals | |
990 | // since that is already done by MkLines.Check. | 1006 | // since that is already done by MkLines.Check. | |
991 | t.CheckOutputLines( | 1007 | t.CheckOutputLines( | |
992 | "WARN: filename.mk:EOF: Incomplete SUBST block: SUBST_FILES.1 missing.", | 1008 | "WARN: filename.mk:EOF: Incomplete SUBST block: SUBST_FILES.1 missing.", | |
993 | "WARN: filename.mk:EOF: Incomplete SUBST block: "+ | 1009 | "WARN: filename.mk:EOF: Incomplete SUBST block: "+ |
@@ -1145,27 +1145,27 @@ func (reg *VarTypeRegistry) Init(src *Pk | @@ -1145,27 +1145,27 @@ func (reg *VarTypeRegistry) Init(src *Pk | |||
1145 | reg.sys("EMUL_OPSYS", enum("darwin freebsd hpux irix linux osf1 solaris sunos none")) | 1145 | reg.sys("EMUL_OPSYS", enum("darwin freebsd hpux irix linux osf1 solaris sunos none")) | |
1146 | reg.pkg("EMUL_PKG_FMT", enum("plain rpm")) | 1146 | reg.pkg("EMUL_PKG_FMT", enum("plain rpm")) | |
1147 | reg.usr("EMUL_PLATFORM", BtEmulPlatform) | 1147 | reg.usr("EMUL_PLATFORM", BtEmulPlatform) | |
1148 | reg.pkglist("EMUL_PLATFORMS", BtEmulPlatform) | 1148 | reg.pkglist("EMUL_PLATFORMS", BtEmulPlatform) | |
1149 | reg.usrlist("EMUL_PREFER", BtEmulPlatform) | 1149 | reg.usrlist("EMUL_PREFER", BtEmulPlatform) | |
1150 | reg.pkglist("EMUL_REQD", BtDependencyPattern) | 1150 | reg.pkglist("EMUL_REQD", BtDependencyPattern) | |
1151 | reg.usr("EMUL_TYPE.*", enum("native builtin suse suse-10.0 suse-12.1 suse-13.1")) | 1151 | reg.usr("EMUL_TYPE.*", enum("native builtin suse suse-10.0 suse-12.1 suse-13.1")) | |
1152 | reg.sys("ERROR_CAT", BtShellCommand) | 1152 | reg.sys("ERROR_CAT", BtShellCommand) | |
1153 | reg.sys("ERROR_MSG", BtShellCommand) | 1153 | reg.sys("ERROR_MSG", BtShellCommand) | |
1154 | reg.syslist("EXPORT_SYMBOLS_LDFLAGS", BtLdFlag) | 1154 | reg.syslist("EXPORT_SYMBOLS_LDFLAGS", BtLdFlag) | |
1155 | reg.sys("EXTRACT_CMD", BtShellCommand) | 1155 | reg.sys("EXTRACT_CMD", BtShellCommand) | |
1156 | reg.pkg("EXTRACT_DIR", BtPathname) | 1156 | reg.pkg("EXTRACT_DIR", BtPathname) | |
1157 | reg.pkg("EXTRACT_DIR.*", BtPathname) | 1157 | reg.pkg("EXTRACT_DIR.*", BtPathname) | |
1158 | reg.pkglist("EXTRACT_ELEMENTS", BtPathPattern) // TODO: No slashes allowed | 1158 | reg.pkglist("EXTRACT_ELEMENTS", BtPathPattern) | |
1159 | reg.pkglist("EXTRACT_ENV", BtShellWord) | 1159 | reg.pkglist("EXTRACT_ENV", BtShellWord) | |
1160 | reg.pkglist("EXTRACT_ONLY", BtPathname) | 1160 | reg.pkglist("EXTRACT_ONLY", BtPathname) | |
1161 | reg.pkglist("EXTRACT_OPTS", BtShellWord) | 1161 | reg.pkglist("EXTRACT_OPTS", BtShellWord) | |
1162 | reg.pkglist("EXTRACT_OPTS_BIN", BtShellWord) | 1162 | reg.pkglist("EXTRACT_OPTS_BIN", BtShellWord) | |
1163 | reg.pkglist("EXTRACT_OPTS_LHA", BtShellWord) | 1163 | reg.pkglist("EXTRACT_OPTS_LHA", BtShellWord) | |
1164 | reg.pkglist("EXTRACT_OPTS_PAX", BtShellWord) | 1164 | reg.pkglist("EXTRACT_OPTS_PAX", BtShellWord) | |
1165 | reg.pkglist("EXTRACT_OPTS_RAR", BtShellWord) | 1165 | reg.pkglist("EXTRACT_OPTS_RAR", BtShellWord) | |
1166 | reg.pkglist("EXTRACT_OPTS_TAR", BtShellWord) | 1166 | reg.pkglist("EXTRACT_OPTS_TAR", BtShellWord) | |
1167 | reg.pkglist("EXTRACT_OPTS_ZIP", BtShellWord) | 1167 | reg.pkglist("EXTRACT_OPTS_ZIP", BtShellWord) | |
1168 | reg.pkglist("EXTRACT_OPTS_ZOO", BtShellWord) | 1168 | reg.pkglist("EXTRACT_OPTS_ZOO", BtShellWord) | |
1169 | reg.pkg("EXTRACT_SUFX", BtDistSuffix) | 1169 | reg.pkg("EXTRACT_SUFX", BtDistSuffix) | |
1170 | reg.sys("FAIL_MSG", BtShellCommand) | 1170 | reg.sys("FAIL_MSG", BtShellCommand) | |
1171 | reg.sys("FAMBASE", BtPathname) | 1171 | reg.sys("FAMBASE", BtPathname) |
@@ -17,185 +17,265 @@ type state struct { | @@ -17,185 +17,265 @@ type state struct { | |||
17 | end bool | 17 | end bool | |
18 | } | 18 | } | |
19 | 19 | |||
20 | type transition struct { | 20 | type transition struct { | |
21 | min, max byte | 21 | min, max byte | |
22 | to StateID | 22 | to StateID | |
23 | } | 23 | } | |
24 | 24 | |||
25 | type StateID uint16 | 25 | type StateID uint16 | |
26 | 26 | |||
27 | // Compile parses a pattern, including the error checking that is missing | 27 | // Compile parses a pattern, including the error checking that is missing | |
28 | // from bmake. | 28 | // from bmake. | |
29 | func Compile(pattern string) (*Pattern, error) { | 29 | func Compile(pattern string) (*Pattern, error) { | |
30 | var a Pattern | 30 | var p Pattern | |
31 | s := a.AddState(false) | 31 | s := p.AddState(false) | |
32 | ||||
33 | var deadEnd StateID | |||
34 | 32 | |||
35 | lex := textproc.NewLexer(pattern) | 33 | lex := textproc.NewLexer(pattern) | |
36 | for !lex.EOF() { | 34 | for !lex.EOF() { | |
37 | 35 | |||
38 | if lex.SkipByte('*') { | 36 | if lex.SkipByte('*') { | |
39 | a.AddTransition(s, 0, 255, s) | 37 | p.AddTransition(s, 0, 255, s) | |
40 | continue | 38 | continue | |
41 | } | 39 | } | |
42 | 40 | |||
43 | if lex.SkipByte('?') { | 41 | if lex.SkipByte('?') { | |
44 | next := a.AddState(false) | 42 | next := p.AddState(false) | |
45 | a.AddTransition(s, 0, 255, next) | 43 | p.AddTransition(s, 0, 255, next) | |
46 | s = next | 44 | s = next | |
47 | continue | 45 | continue | |
48 | } | 46 | } | |
49 | 47 | |||
50 | if lex.SkipByte('\\') { | 48 | if lex.SkipByte('\\') { | |
51 | if lex.EOF() { | 49 | if lex.EOF() { | |
52 | return nil, errors.New("unfinished escape sequence") | 50 | return nil, errors.New("unfinished escape sequence") | |
53 | } | 51 | } | |
54 | ch := lex.NextByte() | 52 | ch := lex.NextByte() | |
55 | next := a.AddState(false) | 53 | next := p.AddState(false) | |
56 | a.AddTransition(s, ch, ch, next) | 54 | p.AddTransition(s, ch, ch, next) | |
57 | s = next | 55 | s = next | |
58 | continue | 56 | continue | |
59 | } | 57 | } | |
60 | 58 | |||
61 | ch := lex.NextByte() | 59 | ch := lex.NextByte() | |
62 | if ch != '[' { | 60 | if ch != '[' { | |
63 | next := a.AddState(false) | 61 | next := p.AddState(false) | |
64 | a.AddTransition(s, ch, ch, next) | 62 | p.AddTransition(s, ch, ch, next) | |
65 | s = next | 63 | s = next | |
66 | continue | 64 | continue | |
67 | } | 65 | } | |
68 | 66 | |||
69 | negate := lex.SkipByte('^') | 67 | next, err := compileCharClass(&p, lex, ch, s) | |
70 | if negate && deadEnd == 0 { | 68 | if err != nil { | |
71 | deadEnd = a.AddState(false) | 69 | return nil, err | |
70 | } | |||
71 | ||||
72 | s = next | |||
73 | } | |||
74 | ||||
75 | p.states[s].end = true | |||
76 | return &p, nil | |||
77 | } | |||
78 | ||||
79 | func compileCharClass(p *Pattern, lex *textproc.Lexer, ch byte, s StateID) (StateID, error) { | |||
80 | negate := lex.SkipByte('^') | |||
81 | chars := make([]bool, 256) | |||
82 | next := p.AddState(false) | |||
83 | for { | |||
84 | if lex.EOF() { | |||
85 | return 0, errors.New("unfinished character class") | |||
86 | } | |||
87 | ch = lex.NextByte() | |||
88 | if ch == ']' { | |||
89 | break | |||
72 | } | 90 | } | |
73 | next := a.AddState(false) | 91 | if lex.SkipByte('-') { | |
74 | for { | |||
75 | if lex.EOF() { | 92 | if lex.EOF() { | |
76 | return nil, errors.New("unfinished character class") | 93 | return 0, errors.New("unfinished character range") | |
77 | } | 94 | } | |
78 | ch = lex.NextByte() | 95 | max := lex.NextByte() | |
79 | if ch == ']' { | 96 | if ch > max { | |
80 | break | 97 | ch, max = max, ch | |
81 | } | |||
82 | max := ch | |||
83 | if lex.SkipByte('-') { | |||
84 | if lex.EOF() { | |||
85 | return nil, errors.New("unfinished character range") | |||
86 | } | |||
87 | max = lex.NextByte() | |||
88 | } | 98 | } | |
89 | 99 | for i := int(ch); i <= int(max); i++ { | ||
90 | to := next | 100 | chars[i] = true | |
91 | if negate { | |||
92 | to = deadEnd | |||
93 | } | 101 | } | |
94 | a.AddTransition(s, bmin(ch, max), bmax(ch, max), to) | 102 | } else { | |
103 | chars[ch] = true | |||
95 | } | 104 | } | |
96 | if negate { | 105 | } | |
97 | a.AddTransition(s, 0, 255, next) | 106 | if negate { | |
107 | for i, b := range chars { | |||
108 | chars[i] = !b | |||
98 | } | 109 | } | |
99 | s = next | |||
100 | } | 110 | } | |
101 | 111 | |||
102 | a.states[s].end = true | 112 | start := 0 | |
103 | return &a, nil | 113 | for start < len(chars) && !chars[start] { | |
114 | start++ | |||
115 | } | |||
116 | ||||
117 | for start < len(chars) { | |||
118 | end := start | |||
119 | for end < len(chars) && chars[end] { | |||
120 | end++ | |||
121 | } | |||
122 | ||||
123 | if start < end { | |||
124 | p.AddTransition(s, byte(start), byte(end-1), next) | |||
125 | } | |||
126 | ||||
127 | start = end | |||
128 | for start < len(chars) && !chars[start] { | |||
129 | start++ | |||
130 | } | |||
131 | } | |||
132 | return next, nil | |||
104 | } | 133 | } | |
105 | 134 | |||
106 | func (a *Pattern) AddState(end bool) StateID { | 135 | func (p *Pattern) AddState(end bool) StateID { | |
107 | a.states = append(a.states, state{nil, end}) | 136 | p.states = append(p.states, state{nil, end}) | |
108 | return StateID(len(a.states) - 1) | 137 | return StateID(len(p.states) - 1) | |
109 | } | 138 | } | |
110 | 139 | |||
111 | func (a *Pattern) AddTransition(from StateID, min, max byte, to StateID) { | 140 | func (p *Pattern) AddTransition(from StateID, min, max byte, to StateID) { | |
112 | state := &a.states[from] | 141 | state := &p.states[from] | |
113 | state.transitions = append(state.transitions, transition{min, max, to}) | 142 | state.transitions = append(state.transitions, transition{min, max, to}) | |
114 | } | 143 | } | |
115 | 144 | |||
116 | // Match tests whether a pattern matches the given string. | 145 | // Match tests whether a pattern matches the given string. | |
117 | func (a *Pattern) Match(s string) bool { | 146 | func (p *Pattern) Match(s string) bool { | |
118 | state := StateID(0) | 147 | curr := make([]bool, len(p.states)) | |
148 | next := make([]bool, len(p.states)) | |||
149 | ||||
150 | curr[0] = true | |||
119 | for _, ch := range []byte(s) { | 151 | for _, ch := range []byte(s) { | |
120 | for _, tr := range a.states[state].transitions { | 152 | ok := false | |
121 | if tr.min <= ch && ch <= tr.max { | 153 | for i, _ := range next { | |
122 | state = tr.to | 154 | next[i] = false | |
123 | goto nextByte | 155 | } | |
156 | ||||
157 | for si := range curr { | |||
158 | if !curr[si] { | |||
159 | continue | |||
160 | } | |||
161 | for _, tr := range p.states[si].transitions { | |||
162 | if tr.min <= ch && ch <= tr.max { | |||
163 | next[tr.to] = true | |||
164 | ok = true | |||
165 | } | |||
124 | } | 166 | } | |
125 | } | 167 | } | |
126 | return false | 168 | if !ok { | |
127 | nextByte: | 169 | return false | |
170 | } | |||
171 | curr, next = next, curr | |||
128 | } | 172 | } | |
129 | return a.states[state].end | 173 | ||
174 | for i, curr := range curr { | |||
175 | if curr && p.states[i].end { | |||
176 | return true | |||
177 | } | |||
178 | } | |||
179 | return false | |||
130 | } | 180 | } | |
131 | 181 | |||
132 | // Intersect computes a pattern that only matches if both given patterns | 182 | // Intersect computes a pattern that only matches if both given patterns | |
133 | // match at the same time. | 183 | // match at the same time. | |
134 | func Intersect(a, b *Pattern) *Pattern { | 184 | func Intersect(p1, p2 *Pattern) *Pattern { | |
135 | var is Pattern | 185 | var res Pattern | |
136 | for i := 0; i < len(a.states); i++ { | 186 | for i1 := 0; i1 < len(p1.states); i1++ { | |
137 | for j := 0; j < len(b.states); j++ { | 187 | for i2 := 0; i2 < len(p2.states); i2++ { | |
138 | is.AddState(a.states[i].end && b.states[j].end) | 188 | res.AddState(p1.states[i1].end && p2.states[i2].end) | |
139 | } | 189 | } | |
140 | } | 190 | } | |
141 | 191 | |||
142 | for i := 0; i < len(a.states); i++ { | 192 | for i1 := 0; i1 < len(p1.states); i1++ { | |
143 | for j := 0; j < len(b.states); j++ { | 193 | for i2 := 0; i2 < len(p2.states); i2++ { | |
144 | for _, at := range a.states[i].transitions { | 194 | for _, t1 := range p1.states[i1].transitions { | |
145 | for _, bt := range b.states[j].transitions { | 195 | for _, t2 := range p2.states[i2].transitions { | |
146 | min := bmax(at.min, bt.min) | 196 | min := bmax(t1.min, t2.min) | |
147 | max := bmin(at.max, bt.max) | 197 | max := bmin(t1.max, t2.max) | |
148 | if min <= max { | 198 | if min <= max { | |
149 | from := StateID(i*len(b.states) + j) | 199 | from := StateID(i1*len(p2.states) + i2) | |
150 | to := at.to*StateID(len(b.states)) + bt.to | 200 | to := t1.to*StateID(len(p2.states)) + t2.to | |
151 | is.AddTransition(from, min, max, to) | 201 | res.AddTransition(from, min, max, to) | |
152 | } | 202 | } | |
153 | } | 203 | } | |
154 | } | 204 | } | |
155 | } | 205 | } | |
156 | } | 206 | } | |
157 | 207 | |||
158 | // TODO: optimize: remove transitions that point to a dead end | 208 | return res.optimized() | |
209 | } | |||
210 | ||||
211 | func (p *Pattern) optimized() *Pattern { | |||
212 | var opt Pattern | |||
213 | ||||
214 | var todo []StateID | |||
215 | hasNewID := make([]bool, len(p.states)) | |||
216 | newIDs := make([]StateID, len(p.states)) | |||
217 | ||||
218 | todo = append(todo, 0) | |||
219 | newIDs[0] = opt.AddState(p.states[0].end) | |||
220 | hasNewID[0] = true | |||
221 | ||||
222 | for len(todo) > 0 { | |||
223 | oldStateID := todo[len(todo)-1] | |||
224 | todo = todo[:len(todo)-1] | |||
225 | ||||
226 | oldState := p.states[oldStateID] | |||
227 | ||||
228 | for _, t := range oldState.transitions { | |||
229 | if !hasNewID[t.to] { | |||
230 | hasNewID[t.to] = true | |||
231 | newIDs[t.to] = opt.AddState(p.states[t.to].end) | |||
232 | todo = append(todo, t.to) | |||
233 | } | |||
234 | opt.AddTransition(newIDs[oldStateID], t.min, t.max, newIDs[t.to]) | |||
235 | } | |||
236 | } | |||
237 | ||||
238 | // TODO: remove transitions that point to a dead end | |||
159 | 239 | |||
160 | return &is | 240 | return &opt | |
161 | } | 241 | } | |
162 | 242 | |||
163 | // CanMatch tests whether the pattern can match some string. | 243 | // CanMatch tests whether the pattern can match some string. | |
164 | // Most patterns can do that. | 244 | // Most patterns can do that. | |
165 | // Typical counterexamples are: | 245 | // Typical counterexamples are: | |
166 | // [^] | 246 | // [^] | |
167 | // Intersect("*.c", "*.h") | 247 | // Intersect("*.c", "*.h") | |
168 | func (a *Pattern) CanMatch() bool { | 248 | func (p *Pattern) CanMatch() bool { | |
169 | reachable := make([]bool, len(a.states)) | 249 | reachable := make([]bool, len(p.states)) | |
170 | reachable[0] = true | 250 | reachable[0] = true | |
171 | 251 | |||
172 | again: | 252 | again: | |
173 | changed := false | 253 | changed := false | |
174 | for i, s := range a.states { | 254 | for i, s := range p.states { | |
175 | if reachable[i] { | 255 | if reachable[i] { | |
176 | for _, t := range s.transitions { | 256 | for _, t := range s.transitions { | |
177 | if !reachable[t.to] { | 257 | if !reachable[t.to] { | |
178 | reachable[t.to] = true | 258 | reachable[t.to] = true | |
179 | changed = true | 259 | changed = true | |
180 | } | 260 | } | |
181 | } | 261 | } | |
182 | } | 262 | } | |
183 | } | 263 | } | |
184 | if changed { | 264 | if changed { | |
185 | goto again | 265 | goto again | |
186 | } | 266 | } | |
187 | 267 | |||
188 | for i, s := range a.states { | 268 | for i, s := range p.states { | |
189 | if reachable[i] && s.end { | 269 | if reachable[i] && s.end { | |
190 | return true | 270 | return true | |
191 | } | 271 | } | |
192 | } | 272 | } | |
193 | return false | 273 | return false | |
194 | } | 274 | } | |
195 | 275 | |||
196 | func bmin(a, b byte) byte { | 276 | func bmin(a, b byte) byte { | |
197 | if a < b { | 277 | if a < b { | |
198 | return a | 278 | return a | |
199 | } | 279 | } | |
200 | return b | 280 | return b | |
201 | } | 281 | } |
@@ -1,20 +1,97 @@ | @@ -1,20 +1,97 @@ | |||
1 | package makepat | 1 | package makepat | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | "netbsd.org/pkglint/intqa" | |||
5 | "reflect" | |||
4 | "testing" | 6 | "testing" | |
5 | ) | 7 | ) | |
6 | 8 | |||
7 | func Test_Automaton_Match(t *testing.T) { | 9 | func Test_Compile__errors(t *testing.T) { | |
10 | tests := []struct { | |||
11 | pattern string | |||
12 | msg string | |||
13 | }{ | |||
14 | {"\\", "unfinished escape sequence"}, | |||
15 | {"[", "unfinished character class"}, | |||
16 | {"[a-", "unfinished character range"}, | |||
17 | {"[\\", "unfinished character class"}, | |||
18 | } | |||
19 | for _, tt := range tests { | |||
20 | t.Run(tt.pattern, func(t *testing.T) { | |||
21 | _, err := Compile(tt.pattern) | |||
22 | if err == nil { | |||
23 | t.Fail() | |||
24 | } else if err.Error() != tt.msg { | |||
25 | t.Errorf("err = %v, want %v", err, tt.msg) | |||
26 | } | |||
27 | }) | |||
28 | } | |||
29 | } | |||
30 | ||||
31 | func Test_compileCharClass(t *testing.T) { | |||
32 | tests := []struct { | |||
33 | pattern string | |||
34 | str string | |||
35 | want bool | |||
36 | }{ | |||
37 | {"[0-9]", "/", false}, | |||
38 | {"[0-9]", "0", true}, | |||
39 | {"[0-9]", "9", true}, | |||
40 | {"[0-9]", ":", false}, | |||
41 | } | |||
42 | for _, tt := range tests { | |||
43 | t.Run(tt.pattern, func(t *testing.T) { | |||
44 | p, err := Compile(tt.pattern) | |||
45 | if err != nil { | |||
46 | t.Fail() | |||
47 | } | |||
48 | got := p.Match(tt.str) | |||
49 | if got != tt.want { | |||
50 | t.Errorf("got %v, want %v", got, tt.want) | |||
51 | } | |||
52 | }) | |||
53 | } | |||
54 | } | |||
55 | ||||
56 | func Test_Pattern_AddState(t *testing.T) { | |||
57 | var p Pattern | |||
58 | ||||
59 | p.AddState(false) | |||
60 | p.AddState(true) | |||
61 | ||||
62 | expected := Pattern{states: []state{{nil, false}, {nil, true}}} | |||
63 | if !reflect.DeepEqual(p, expected) { | |||
64 | t.Errorf("%#v", p) | |||
65 | } | |||
66 | } | |||
67 | ||||
68 | func Test_Pattern_AddTransition(t *testing.T) { | |||
69 | var p Pattern | |||
70 | ||||
71 | p.AddState(false) | |||
72 | p.AddState(true) | |||
73 | p.AddTransition(0, '0', '9', 1) | |||
74 | p.AddTransition(1, '0', '9', 0) | |||
75 | ||||
76 | expected := Pattern{states: []state{ | |||
77 | {[]transition{{'0', '9', 1}}, false}, | |||
78 | {[]transition{{'0', '9', 0}}, true}}} | |||
79 | if !reflect.DeepEqual(p, expected) { | |||
80 | t.Errorf("%#v", p) | |||
81 | } | |||
82 | } | |||
83 | ||||
84 | func Test_Pattern_Match(t *testing.T) { | |||
8 | tests := []struct { | 85 | tests := []struct { | |
9 | pattern string | 86 | pattern string | |
10 | str string | 87 | str string | |
11 | want bool | 88 | want bool | |
12 | }{ | 89 | }{ | |
13 | {"a-[0-9]*", "", false}, | 90 | {"a-[0-9]*", "", false}, | |
14 | {"a-[0-9]*", "a", false}, | 91 | {"a-[0-9]*", "a", false}, | |
15 | {"a-[0-9]*", "b", false}, | 92 | {"a-[0-9]*", "b", false}, | |
16 | {"a-[0-9]*", "a-", false}, | 93 | {"a-[0-9]*", "a-", false}, | |
17 | {"a-[0-9]*", "a-0", true}, | 94 | {"a-[0-9]*", "a-0", true}, | |
18 | {"a-[0-9]*", "a-13", true}, | 95 | {"a-[0-9]*", "a-13", true}, | |
19 | {"a-[0-9]*", "a-3* matches arbitrary text", true}, | 96 | {"a-[0-9]*", "a-3* matches arbitrary text", true}, | |
20 | {"a-[0-9]*", "a+", false}, | 97 | {"a-[0-9]*", "a+", false}, | |
@@ -57,62 +134,46 @@ func Test_Automaton_Match(t *testing.T) | @@ -57,62 +134,46 @@ func Test_Automaton_Match(t *testing.T) | |||
57 | {"[0-9A-Za-z]", "z", true}, | 134 | {"[0-9A-Za-z]", "z", true}, | |
58 | {"[0-9A-Za-z]", "{", false}, | 135 | {"[0-9A-Za-z]", "{", false}, | |
59 | {"[\\-]]", "{", false}, | 136 | {"[\\-]]", "{", false}, | |
60 | {"[\\-]]", "\\", true}, | 137 | {"[\\-]]", "\\", true}, | |
61 | {"[\\-]]", "]", true}, | 138 | {"[\\-]]", "]", true}, | |
62 | {"[\\-]]", "^", false}, | 139 | {"[\\-]]", "^", false}, | |
63 | {"[9-0]", "", false}, | 140 | {"[9-0]", "", false}, | |
64 | {"[9-0]", "55", false}, | 141 | {"[9-0]", "55", false}, | |
65 | {"[9-0]", "/", false}, | 142 | {"[9-0]", "/", false}, | |
66 | {"[9-0]", "0", true}, | 143 | {"[9-0]", "0", true}, | |
67 | {"[9-0]", "5", true}, | 144 | {"[9-0]", "5", true}, | |
68 | {"[9-0]", "9", true}, | 145 | {"[9-0]", "9", true}, | |
69 | {"[9-0]", ":", false}, | 146 | {"[9-0]", ":", false}, | |
147 | {"*.c", ".c", true}, | |||
148 | {"*.c", "a.c", true}, | |||
149 | {"*.c", "c.c", true}, | |||
150 | {"*.c", "..c", true}, | |||
151 | {"*.c", ".c.c", true}, | |||
152 | {"*.c", "a.c.c", true}, | |||
70 | } | 153 | } | |
71 | for _, tt := range tests { | 154 | for _, tt := range tests { | |
72 | t.Run(tt.pattern+" "+tt.str, func(t *testing.T) { | 155 | t.Run(tt.pattern+" "+tt.str, func(t *testing.T) { | |
73 | a, err := Compile(tt.pattern) | 156 | a, err := Compile(tt.pattern) | |
74 | if err != nil { | 157 | if err != nil { | |
75 | t.Fatal(err) | 158 | t.Fatal(err) | |
76 | } | 159 | } | |
77 | if got := a.Match(tt.str); got != tt.want { | 160 | if got := a.Match(tt.str); got != tt.want { | |
78 | t.Errorf("Match() = %v, want %v", got, tt.want) | 161 | t.Errorf("Match() = %v, want %v", got, tt.want) | |
79 | } | 162 | } | |
80 | }) | 163 | }) | |
81 | } | 164 | } | |
82 | } | 165 | } | |
83 | 166 | |||
84 | func Test_Automaton_Compile__errors(t *testing.T) { | |||
85 | tests := []struct { | |||
86 | pattern string | |||
87 | msg string | |||
88 | }{ | |||
89 | {"\\", "unfinished escape sequence"}, | |||
90 | {"[", "unfinished character class"}, | |||
91 | {"[a-", "unfinished character range"}, | |||
92 | {"[\\", "unfinished character class"}, | |||
93 | } | |||
94 | for _, tt := range tests { | |||
95 | t.Run(tt.pattern, func(t *testing.T) { | |||
96 | _, err := Compile(tt.pattern) | |||
97 | if err == nil { | |||
98 | t.Fail() | |||
99 | } else if err.Error() != tt.msg { | |||
100 | t.Errorf("err = %v, want %v", err, tt.msg) | |||
101 | } | |||
102 | }) | |||
103 | } | |||
104 | } | |||
105 | ||||
106 | func Test_Intersect(t *testing.T) { | 167 | func Test_Intersect(t *testing.T) { | |
107 | tests := []struct { | 168 | tests := []struct { | |
108 | pattern1 string | 169 | pattern1 string | |
109 | pattern2 string | 170 | pattern2 string | |
110 | str string | 171 | str string | |
111 | matches bool | 172 | matches bool | |
112 | canMatch bool | 173 | canMatch bool | |
113 | }{ | 174 | }{ | |
114 | {"N-*", "N-*", "N-*", true, true}, | 175 | {"N-*", "N-*", "N-*", true, true}, | |
115 | {"N-9.99.*", "N-[1-9].*", "", false, true}, | 176 | {"N-9.99.*", "N-[1-9].*", "", false, true}, | |
116 | {"N-9.99.*", "N-[1-9][0-9].*", "", false, false}, | 177 | {"N-9.99.*", "N-[1-9][0-9].*", "", false, false}, | |
117 | } | 178 | } | |
118 | for _, tt := range tests { | 179 | for _, tt := range tests { | |
@@ -127,13 +188,89 @@ func Test_Intersect(t *testing.T) { | @@ -127,13 +188,89 @@ func Test_Intersect(t *testing.T) { | |||
127 | } | 188 | } | |
128 | a := Intersect(a1, a2) | 189 | a := Intersect(a1, a2) | |
129 | matches := a.Match(tt.str) | 190 | matches := a.Match(tt.str) | |
130 | if matches != tt.matches { | 191 | if matches != tt.matches { | |
131 | t.Errorf("Match() = %v, want %v", matches, tt.matches) | 192 | t.Errorf("Match() = %v, want %v", matches, tt.matches) | |
132 | } | 193 | } | |
133 | canMatch := a.CanMatch() | 194 | canMatch := a.CanMatch() | |
134 | if canMatch != tt.canMatch { | 195 | if canMatch != tt.canMatch { | |
135 | t.Errorf("CanMatch() = %v, want %v", canMatch, tt.canMatch) | 196 | t.Errorf("CanMatch() = %v, want %v", canMatch, tt.canMatch) | |
136 | } | 197 | } | |
137 | }) | 198 | }) | |
138 | } | 199 | } | |
139 | } | 200 | } | |
201 | ||||
202 | func Test_Pattern_optimized(t *testing.T) { | |||
203 | var p Pattern | |||
204 | p.AddState(false) | |||
205 | p.AddState(false) | |||
206 | p.AddState(false) | |||
207 | p.AddState(true) | |||
208 | p.AddTransition(0, '1', '1', 1) | |||
209 | p.AddTransition(1, '2', '2', 3) | |||
210 | ||||
211 | opt := p.optimized() | |||
212 | ||||
213 | expected := Pattern{[]state{ | |||
214 | {[]transition{{'1', '1', 1}}, false}, | |||
215 | {[]transition{{'2', '2', 2}}, false}, | |||
216 | {nil, true}}} | |||
217 | if !reflect.DeepEqual(opt, &expected) { | |||
218 | t.Errorf("%#v", p) | |||
219 | } | |||
220 | } | |||
221 | ||||
222 | func Test_Pattern_CanMatch(t *testing.T) { | |||
223 | tests := []struct { | |||
224 | p1 string | |||
225 | p2 string | |||
226 | want bool | |||
227 | }{ | |||
228 | {"*.c", "*.h", false}, | |||
229 | {"*.c", "????.?", true}, | |||
230 | {"[1-9]", "5", true}, | |||
231 | {"[1-9]", ":", false}, | |||
232 | {"[1-9A-Za-z]", "[ -/]", false}, | |||
233 | } | |||
234 | for _, tt := range tests { | |||
235 | t.Run(tt.p1+" "+tt.p2, func(t *testing.T) { | |||
236 | p1, err1 := Compile(tt.p1) | |||
237 | p2, err2 := Compile(tt.p2) | |||
238 | if err1 != nil { | |||
239 | t.Fatal(err1) | |||
240 | } | |||
241 | if err2 != nil { | |||
242 | t.Fatal(err2) | |||
243 | } | |||
244 | both := Intersect(p1, p2) | |||
245 | got := both.CanMatch() | |||
246 | if got != tt.want { | |||
247 | t.Errorf("CanMatch() = %v, want %v", got, tt.want) | |||
248 | } | |||
249 | }) | |||
250 | } | |||
251 | ||||
252 | } | |||
253 | ||||
254 | func Test_bmin(t *testing.T) { | |||
255 | if bmin(0, 255) != 0 { | |||
256 | t.Error() | |||
257 | } | |||
258 | if bmin(128, 127) != 127 { | |||
259 | t.Error() | |||
260 | } | |||
261 | } | |||
262 | ||||
263 | func Test_bmax(t *testing.T) { | |||
264 | if bmax(0, 255) != 255 { | |||
265 | t.Error() | |||
266 | } | |||
267 | if bmax(128, 127) != 128 { | |||
268 | t.Error() | |||
269 | } | |||
270 | } | |||
271 | ||||
272 | func Test(t *testing.T) { | |||
273 | ck := intqa.NewQAChecker(t.Errorf) | |||
274 | ck.Configure("*", "*", "", -intqa.EMissingTest) | |||
275 | ck.Check() | |||
276 | } |