Annotation of pkgsrc/pkgtools/pkglint/files/util_test.go, Revision 1.37
1.19 rillig 1: package pkglint
1.1 rillig 2:
3: import (
1.30 rillig 4: "errors"
1.9 rillig 5: "gopkg.in/check.v1"
1.13 rillig 6: "os"
1.6 rillig 7: "testing"
1.13 rillig 8: "time"
1.1 rillig 9: )
10:
1.35 rillig 11: func (s *Suite) Test_YesNoUnknown_String(c *check.C) {
12: t := s.Init(c)
13:
14: t.CheckEquals(yes.String(), "yes")
15: t.CheckEquals(no.String(), "no")
16: t.CheckEquals(unknown.String(), "unknown")
17: }
18:
19: func (s *Suite) Test_trimHspace(c *check.C) {
20: t := s.Init(c)
21:
22: t.CheckEquals(trimHspace("a b"), "a b")
23: t.CheckEquals(trimHspace(" a b "), "a b")
24: t.CheckEquals(trimHspace("\ta b\t"), "a b")
25: t.CheckEquals(trimHspace(" \t a b\t \t"), "a b")
26: }
27:
28: func (s *Suite) Test_trimCommon(c *check.C) {
29: t := s.Init(c)
30:
31: test := func(a, b, trimmedA, trimmedB string) {
32: ta, tb := trimCommon(a, b)
33: t.CheckEquals(ta, trimmedA)
34: t.CheckEquals(tb, trimmedB)
35: }
36:
37: test("", "",
38: "", "")
39:
40: test("equal", "equal",
41: "", "")
42:
43: test("prefixA", "prefixB",
44: "A", "B")
45:
46: test("ASuffix", "BSuffix",
47: "A", "B")
48:
49: test("PreMiddlePost", "PreCenterPost",
50: "Middle", "Center")
51:
52: test("", "b",
53: "", "b")
54:
55: test("a", "",
56: "a", "")
57: }
58:
1.30 rillig 59: func (s *Suite) Test_assertNil(c *check.C) {
60: t := s.Init(c)
61:
62: assertNil(nil, "this is not an error")
63:
64: t.ExpectPanic(
65: func() { assertNil(errors.New("unexpected error"), "Oops") },
66: "Pkglint internal error: Oops: unexpected error")
67: }
68:
1.31 rillig 69: func (s *Suite) Test_assertNotNil(c *check.C) {
70: t := s.Init(c)
71:
72: assertNotNil("this string is not nil")
73:
74: t.ExpectPanic(
75: func() { assertNotNil(nil) },
76: "Pkglint internal error: unexpected nil pointer")
77: t.ExpectPanic(
78: func() { var ptr *string; assertNotNil(ptr) },
79: "Pkglint internal error: unexpected nil pointer")
80: }
81:
1.35 rillig 82: func (s *Suite) Test_isEmptyDir(c *check.C) {
83: t := s.Init(c)
84:
85: t.CreateFileLines("CVS/Entries",
86: "dummy")
87: t.CreateFileLines("subdir/CVS/Entries",
88: "dummy")
89:
90: t.CheckEquals(isEmptyDir(t.File(".")), true)
91: t.CheckEquals(isEmptyDir(t.File("CVS")), true)
1.37 ! rillig 92:
! 93: t.Chdir(".")
! 94:
! 95: t.CheckEquals(isEmptyDir("."), true)
! 96: t.CheckEquals(isEmptyDir("CVS"), true)
1.35 rillig 97: }
98:
99: func (s *Suite) Test_isEmptyDir__and_getSubdirs(c *check.C) {
100: t := s.Init(c)
101:
102: t.CreateFileLines("CVS/Entries",
103: "dummy")
104:
105: if dir := t.File("."); true {
106: t.CheckEquals(isEmptyDir(dir), true)
1.36 rillig 107: t.CheckDeepEquals(getSubdirs(dir), []Path(nil))
1.35 rillig 108:
109: t.CreateFileLines("somedir/file")
110:
111: t.CheckEquals(isEmptyDir(dir), false)
1.36 rillig 112: t.CheckDeepEquals(getSubdirs(dir), []Path{"somedir"})
1.35 rillig 113: }
114:
115: if absent := t.File("nonexistent"); true {
116: t.CheckEquals(isEmptyDir(absent), true) // Counts as empty.
117:
118: // The last group from the error message is localized, therefore the matching.
119: t.ExpectFatalMatches(
120: func() { getSubdirs(absent) },
121: `FATAL: ~/nonexistent: Cannot be read: open ~/nonexistent: (.+)\n`)
122: }
123: }
124:
125: func (s *Suite) Test_getSubdirs(c *check.C) {
126: t := s.Init(c)
127:
128: t.CreateFileLines("subdir/file")
129: t.CreateFileLines("empty/file")
1.36 rillig 130: c.Check(os.Remove(t.File("empty/file").String()), check.IsNil)
1.35 rillig 131:
1.36 rillig 132: t.CheckDeepEquals(getSubdirs(t.File(".")), []Path{"subdir"})
1.35 rillig 133: }
134:
135: func (s *Suite) Test_isLocallyModified(c *check.C) {
1.32 rillig 136: t := s.Init(c)
137:
1.35 rillig 138: unmodified := t.CreateFileLines("unmodified")
139: modTime := time.Unix(1136239445, 0).UTC()
140:
1.36 rillig 141: err := os.Chtimes(unmodified.String(), modTime, modTime)
1.35 rillig 142: c.Check(err, check.IsNil)
143:
1.36 rillig 144: st, err := os.Lstat(unmodified.String())
1.35 rillig 145: c.Check(err, check.IsNil)
146:
147: // Make sure that the file system has second precision and accuracy.
148: t.CheckDeepEquals(st.ModTime().UTC(), modTime)
149:
150: modified := t.CreateFileLines("modified")
151:
152: t.CreateFileLines("CVS/Entries",
153: "/unmodified//"+modTime.Format(time.ANSIC)+"//",
154: "/modified//"+modTime.Format(time.ANSIC)+"//",
155: "/enoent//"+modTime.Format(time.ANSIC)+"//")
156:
157: t.CheckEquals(isLocallyModified(unmodified), false)
158: t.CheckEquals(isLocallyModified(modified), true)
159: t.CheckEquals(isLocallyModified(t.File("enoent")), true)
160: t.CheckEquals(isLocallyModified(t.File("not_mentioned")), false)
161: t.CheckEquals(isLocallyModified(t.File("subdir/file")), false)
162:
163: t.DisableTracing()
164:
165: t.CheckEquals(isLocallyModified(t.File("unmodified")), false)
1.11 rillig 166: }
167:
1.35 rillig 168: func (s *Suite) Test_tabWidth(c *check.C) {
1.32 rillig 169: t := s.Init(c)
170:
1.35 rillig 171: t.CheckEquals(tabWidth("12345"), 5)
172: t.CheckEquals(tabWidth("\t"), 8)
173: t.CheckEquals(tabWidth("123\t"), 8)
174: t.CheckEquals(tabWidth("1234567\t"), 8)
175: t.CheckEquals(tabWidth("12345678\t"), 16)
1.1 rillig 176: }
177:
1.35 rillig 178: // Since tabWidthAppend is used with logical lines (Line.Text) as well as with
179: // raw lines (RawLine.textnl or RawLine.orignl), and since the width only
180: // makes sense for a single line, better panic.
181: func (s *Suite) Test_tabWidthAppend__panic(c *check.C) {
1.32 rillig 182: t := s.Init(c)
183:
1.35 rillig 184: t.ExpectAssert(func() { tabWidthAppend(0, "\n") })
1.1 rillig 185: }
186:
1.35 rillig 187: func (s *Suite) Test_detab(c *check.C) {
1.32 rillig 188: t := s.Init(c)
189:
1.35 rillig 190: t.CheckEquals(detab(""), "")
191: t.CheckEquals(detab("\t"), " ")
192: t.CheckEquals(detab("1234\t9"), "1234 9")
193: t.CheckEquals(detab("1234567\t"), "1234567 ")
194: t.CheckEquals(detab("12345678\t"), "12345678 ")
1.1 rillig 195: }
196:
1.35 rillig 197: func (s *Suite) Test_alignWith(c *check.C) {
1.32 rillig 198: t := s.Init(c)
199:
1.35 rillig 200: test := func(str, other, expected string) {
201: t.CheckEquals(alignWith(str, other), expected)
202: }
203:
204: // At least one tab is _always_ added.
205: test("", "", "\t")
206:
207: test("VAR=", "1234567", "VAR=\t")
208: test("VAR=", "12345678", "VAR=\t")
209: test("VAR=", "123456789", "VAR=\t\t")
210:
211: // At least one tab is added in any case,
212: // even if the other string is shorter.
213: test("1234567890=", "V=", "1234567890=\t")
1.1 rillig 214: }
215:
1.35 rillig 216: func (s *Suite) Test_indent(c *check.C) {
1.32 rillig 217: t := s.Init(c)
218:
1.35 rillig 219: test := func(width int, ind string) {
220: actual := indent(width)
221:
222: t.CheckEquals(actual, ind)
223: }
224:
225: test(0, "")
226: test(1, " ")
227: test(7, " ")
228: test(8, "\t")
229: test(15, "\t ")
230: test(16, "\t\t")
231: test(72, "\t\t\t\t\t\t\t\t\t")
1.1 rillig 232: }
233:
1.35 rillig 234: func (s *Suite) Test_alignmentAfter(c *check.C) {
1.32 rillig 235: t := s.Init(c)
236:
1.35 rillig 237: test := func(prefix string, width int, ind string) {
238: actual := alignmentAfter(prefix, width)
239:
240: t.CheckEquals(actual, ind)
241: }
242:
243: test("", 0, "")
244: test("", 15, "\t ")
245:
246: test(" ", 5, " ")
247: test(" ", 10, "\t ")
248:
249: test("\t", 15, " ")
250: test(" \t", 15, " ")
251: test(" \t", 15, " ")
252: test("\t ", 15, " ")
253:
254: test(" ", 16, "\t\t")
1.1 rillig 255:
1.35 rillig 256: // The desired width must be at least the width of the prefix.
257: t.ExpectAssert(func() { test("\t", 7, "") })
1.1 rillig 258: }
259:
1.13 rillig 260: func (s *Suite) Test_shorten(c *check.C) {
1.32 rillig 261: t := s.Init(c)
262:
263: t.CheckEquals(shorten("aaaaa", 3), "aaa...")
264: t.CheckEquals(shorten("aaaaa", 5), "aaaaa")
265: t.CheckEquals(shorten("aaa", 5), "aaa")
1.13 rillig 266: }
267:
1.35 rillig 268: func (s *Suite) Test_varnameBase(c *check.C) {
269: t := s.Init(c)
270:
271: t.CheckEquals(varnameBase("VAR"), "VAR")
272: t.CheckEquals(varnameBase("VAR.param"), "VAR")
273: t.CheckEquals(varnameBase(".CURDIR"), ".CURDIR")
274: }
275:
276: func (s *Suite) Test_varnameCanon(c *check.C) {
277: t := s.Init(c)
278:
279: t.CheckEquals(varnameCanon("VAR"), "VAR")
280: t.CheckEquals(varnameCanon("VAR.param"), "VAR.*")
281: t.CheckEquals(varnameCanon(".CURDIR"), ".CURDIR")
282: }
283:
284: func (s *Suite) Test_varnameParam(c *check.C) {
285: t := s.Init(c)
286:
287: t.CheckEquals(varnameParam("VAR"), "")
288: t.CheckEquals(varnameParam("VAR.param"), "param")
289: t.CheckEquals(varnameParam(".CURDIR"), "")
290: }
291:
292: func (s *Suite) Test_mkopSubst__middle(c *check.C) {
1.32 rillig 293: t := s.Init(c)
294:
1.35 rillig 295: t.CheckEquals(mkopSubst("pkgname", false, "kgna", false, "ri", ""), "prime")
296: t.CheckEquals(mkopSubst("pkgname", false, "pkgname", false, "replacement", ""), "replacement")
297: t.CheckEquals(mkopSubst("aaaaaaa", false, "a", false, "b", ""), "baaaaaa")
1.1 rillig 298: }
299:
1.35 rillig 300: func (s *Suite) Test_mkopSubst__left(c *check.C) {
1.33 rillig 301: t := s.Init(c)
302:
1.35 rillig 303: t.CheckEquals(mkopSubst("pkgname", true, "kgna", false, "ri", ""), "pkgname")
304: t.CheckEquals(mkopSubst("pkgname", true, "pkgname", false, "replacement", ""), "replacement")
1.33 rillig 305: }
306:
1.35 rillig 307: func (s *Suite) Test_mkopSubst__right(c *check.C) {
1.32 rillig 308: t := s.Init(c)
309:
1.35 rillig 310: t.CheckEquals(mkopSubst("pkgname", false, "kgna", true, "ri", ""), "pkgname")
311: t.CheckEquals(mkopSubst("pkgname", false, "pkgname", true, "replacement", ""), "replacement")
312: }
313:
314: func (s *Suite) Test_mkopSubst__left_and_right(c *check.C) {
315: t := s.Init(c)
1.24 rillig 316:
1.35 rillig 317: t.CheckEquals(mkopSubst("pkgname", true, "kgna", true, "ri", ""), "pkgname")
318: t.CheckEquals(mkopSubst("pkgname", false, "pkgname", false, "replacement", ""), "replacement")
319: }
1.21 rillig 320:
1.35 rillig 321: func (s *Suite) Test_mkopSubst__gflag(c *check.C) {
322: t := s.Init(c)
1.24 rillig 323:
1.35 rillig 324: t.CheckEquals(mkopSubst("aaaaa", false, "a", false, "b", "g"), "bbbbb")
325: t.CheckEquals(mkopSubst("aaaaa", true, "a", false, "b", "g"), "baaaa")
326: t.CheckEquals(mkopSubst("aaaaa", false, "a", true, "b", "g"), "aaaab")
327: t.CheckEquals(mkopSubst("aaaaa", true, "a", true, "b", "g"), "aaaaa")
328: }
1.21 rillig 329:
1.35 rillig 330: func (s *Suite) Test__regex_ReplaceFirst(c *check.C) {
331: t := s.Init(c)
1.21 rillig 332:
1.35 rillig 333: m, rest := G.res.ReplaceFirst("a+b+c+d", `(\w)(.)(\w)`, "X")
1.24 rillig 334:
1.35 rillig 335: c.Assert(m, check.NotNil)
336: t.CheckDeepEquals(m, []string{"a+b", "a", "+", "b"})
337: t.CheckEquals(rest, "X+c+d")
1.24 rillig 338: }
339:
1.35 rillig 340: func (s *Suite) Test_cleanpath(c *check.C) {
341: t := s.Init(c)
342:
1.36 rillig 343: test := func(from, to Path) {
1.35 rillig 344: t.CheckEquals(cleanpath(from), to)
345: }
346:
347: test("simple/path", "simple/path")
348: test("/absolute/path", "/absolute/path")
349:
350: // Single dot components are removed, unless it's the only component of the path.
351: test("./././.", ".")
352: test("./././", ".")
353: test("dir/multi/././/file", "dir/multi/file")
354: test("dir/", "dir")
355:
356: test("dir/", "dir")
357:
358: // Components like aa/bb/../.. are removed, but not in the initial part of the path,
359: // and only if they are not followed by another "..".
360: test("dir/../dir/../dir/../dir/subdir/../../Makefile", "dir/../dir/../dir/../Makefile")
361: test("111/222/../../333/444/../../555/666/../../777/888/9", "111/222/../../777/888/9")
362: test("1/2/3/../../4/5/6/../../7/8/9/../../../../10", "1/2/3/../../4/7/8/9/../../../../10")
363: test("cat/pkg.v1/../../cat/pkg.v2/Makefile", "cat/pkg.v1/../../cat/pkg.v2/Makefile")
364: test("aa/../../../../../a/b/c/d", "aa/../../../../../a/b/c/d")
365: test("aa/bb/../../../../a/b/c/d", "aa/bb/../../../../a/b/c/d")
366: test("aa/bb/cc/../../../a/b/c/d", "aa/bb/cc/../../../a/b/c/d")
367: test("aa/bb/cc/dd/../../a/b/c/d", "aa/bb/a/b/c/d")
368: test("aa/bb/cc/dd/ee/../a/b/c/d", "aa/bb/cc/dd/ee/../a/b/c/d")
369: test("../../../../../a/b/c/d", "../../../../../a/b/c/d")
370: test("aa/../../../../a/b/c/d", "aa/../../../../a/b/c/d")
371: test("aa/bb/../../../a/b/c/d", "aa/bb/../../../a/b/c/d")
372: test("aa/bb/cc/../../a/b/c/d", "aa/bb/cc/../../a/b/c/d")
373: test("aa/bb/cc/dd/../a/b/c/d", "aa/bb/cc/dd/../a/b/c/d")
374: test("aa/../cc/../../a/b/c/d", "aa/../cc/../../a/b/c/d")
375:
376: // The initial 2 components of the path are typically category/package, when
377: // pkglint is called from the pkgsrc top-level directory.
378: // This path serves as the context and therefore is always kept.
379: test("aa/bb/../../cc/dd/../../ee/ff", "aa/bb/../../ee/ff")
380: test("aa/bb/../../cc/dd/../..", "aa/bb/../..")
381: test("aa/bb/cc/dd/../..", "aa/bb")
382: test("aa/bb/../../cc/dd/../../ee/ff/buildlink3.mk", "aa/bb/../../ee/ff/buildlink3.mk")
383: test("./aa/bb/../../cc/dd/../../ee/ff/buildlink3.mk", "aa/bb/../../ee/ff/buildlink3.mk")
384:
385: test("../.", "..")
386: test("../././././././.", "..")
387: test(".././././././././", "..")
388: }
389:
1.31 rillig 390: func (s *Suite) Test_pathContains(c *check.C) {
391: t := s.Init(c)
392:
393: test := func(haystack, needle string, expected bool) {
394: actual := pathContains(haystack, needle)
1.32 rillig 395: t.CheckEquals(actual, expected)
1.31 rillig 396: }
397:
398: testPanic := func(haystack, needle string) {
399: t.c.Check(
400: func() { _ = pathContains(haystack, needle) },
401: check.PanicMatches,
1.32 rillig 402: `runtime error: index out of range.*`)
1.31 rillig 403: }
404:
405: testPanic("", "")
406: testPanic("a", "")
407: testPanic("a/b/c", "")
408:
409: test("a", "a", true)
410: test("a", "b", false)
411: test("a", "A", false)
412: test("a/b/c", "a", true)
413: test("a/b/c", "b", true)
414: test("a/b/c", "c", true)
415: test("a/b/c", "a/b", true)
416: test("a/b/c", "b/c", true)
417: test("a/b/c", "a/b/c", true)
418: test("aa/bb/cc", "a/b", false)
419: test("aa/bb/cc", "a/bb", false)
420: test("aa/bb/cc", "aa/b", false)
421: test("aa/bb/cc", "aa/bb", true)
422: test("aa/bb/cc", "a", false)
423: test("aa/bb/cc", "b", false)
424: test("aa/bb/cc", "c", false)
425: }
426:
427: func (s *Suite) Test_pathContainsDir(c *check.C) {
428: t := s.Init(c)
429:
1.36 rillig 430: test := func(haystack, needle Path, expected bool) {
1.31 rillig 431: actual := pathContainsDir(haystack, needle)
1.32 rillig 432: t.CheckEquals(actual, expected)
1.31 rillig 433: }
434:
1.36 rillig 435: testPanic := func(haystack, needle Path) {
1.31 rillig 436: t.c.Check(
437: func() { _ = pathContainsDir(haystack, needle) },
438: check.PanicMatches,
1.32 rillig 439: `^runtime error: index out of range.*`)
1.31 rillig 440: }
441:
442: testPanic("", "")
443: testPanic("a", "")
444: testPanic("a/b/c", "")
445:
446: test("a", "a", false)
447: test("a", "b", false)
448: test("a", "A", false)
449: test("a/b/c", "a", true)
450: test("a/b/c", "b", true)
451: test("a/b/c", "c", false)
452: test("a/b/c", "a/b", true)
453: test("a/b/c", "b/c", false)
454: test("a/b/c", "a/b/c", false)
455: test("aa/bb/cc", "a/b", false)
456: test("aa/bb/cc", "a/bb", false)
457: test("aa/bb/cc", "aa/b", false)
458: test("aa/bb/cc", "aa/bb", true)
459: test("aa/bb/cc", "a", false)
460: test("aa/bb/cc", "b", false)
461: test("aa/bb/cc", "c", false)
462: }
463:
1.17 rillig 464: const reMkIncludeBenchmark = `^\.([\t ]*)(s?include)[\t ]+\"([^\"]+)\"[\t ]*(?:#.*)?$`
465: const reMkIncludeBenchmarkPositive = `^\.([\t ]*)(s?include)[\t ]+\"(.+)\"[\t ]*(?:#.*)?$`
1.6 rillig 466:
467: func Benchmark_match3_buildlink3(b *testing.B) {
468: for i := 0; i < b.N; i++ {
469: match3(".include \"../../category/package/buildlink3.mk\"", reMkIncludeBenchmark)
470: }
471: }
472:
473: func Benchmark_match3_bsd_pkg_mk(b *testing.B) {
474: for i := 0; i < b.N; i++ {
475: match3(".include \"../../mk/bsd.pkg.mk\"", reMkIncludeBenchmark)
476: }
477: }
478:
1.14 rillig 479: func Benchmark_match3_same_dir(b *testing.B) {
1.6 rillig 480: for i := 0; i < b.N; i++ {
481: match3(".include \"options.mk\"", reMkIncludeBenchmark)
482: }
483: }
484:
485: func Benchmark_match3_bsd_pkg_mk_comment(b *testing.B) {
486: for i := 0; i < b.N; i++ {
487: match3(".include \"../../mk/bsd.pkg.mk\" # infrastructure ", reMkIncludeBenchmark)
488: }
489: }
490:
491: func Benchmark_match3_buildlink3_positive(b *testing.B) {
492: for i := 0; i < b.N; i++ {
493: match3(".include \"../../category/package/buildlink3.mk\"", reMkIncludeBenchmarkPositive)
494: }
495: }
496:
497: func Benchmark_match3_bsd_pkg_mk_positive(b *testing.B) {
498: for i := 0; i < b.N; i++ {
499: match3(".include \"../../mk/bsd.pkg.mk\"", reMkIncludeBenchmarkPositive)
500: }
501: }
502:
1.14 rillig 503: func Benchmark_match3_same_dir_positive(b *testing.B) {
1.6 rillig 504: for i := 0; i < b.N; i++ {
505: match3(".include \"options.mk\"", reMkIncludeBenchmarkPositive)
506: }
507: }
508:
509: func Benchmark_match3_bsd_pkg_mk_comment_positive(b *testing.B) {
510: for i := 0; i < b.N; i++ {
511: match3(".include \"../../mk/bsd.pkg.mk\" # infrastructure ", reMkIncludeBenchmarkPositive)
512: }
513: }
514:
515: func Benchmark_match3_explicit(b *testing.B) {
516: for i := 0; i < b.N; i++ {
517: MatchMkInclude(".include \"../../mk/bsd.pkg.mk\" # infrastructure ")
518: }
519: }
1.9 rillig 520:
521: func emptyToNil(slice []string) []string {
522: if len(slice) == 0 {
523: return nil
524: }
525: return slice
526: }
1.10 rillig 527:
1.35 rillig 528: func (s *Suite) Test_hasAlnumPrefix(c *check.C) {
1.27 rillig 529: t := s.Init(c)
530:
1.35 rillig 531: t.CheckEquals(hasAlnumPrefix(""), false)
532: t.CheckEquals(hasAlnumPrefix("A"), true)
533: t.CheckEquals(hasAlnumPrefix(","), false)
1.32 rillig 534: }
535:
1.35 rillig 536: func (s *Suite) Test_Once(c *check.C) {
1.32 rillig 537: t := s.Init(c)
538:
1.35 rillig 539: var once Once
1.32 rillig 540:
1.35 rillig 541: t.CheckEquals(once.FirstTime("str"), true)
542: t.CheckEquals(once.FirstTime("str"), false)
543: t.CheckEquals(once.FirstTimeSlice("str"), false)
544: t.CheckEquals(once.FirstTimeSlice("str", "str2"), true)
545: t.CheckEquals(once.FirstTimeSlice("str", "str2"), false)
546: }
1.32 rillig 547:
1.35 rillig 548: func (s *Suite) Test_Once__trace(c *check.C) {
549: t := s.Init(c)
1.32 rillig 550:
1.35 rillig 551: var once Once
552: once.Trace = true
1.32 rillig 553:
1.35 rillig 554: t.CheckEquals(once.FirstTime("str"), true)
555: t.CheckEquals(once.FirstTime("str"), false)
556: t.CheckEquals(once.FirstTimeSlice("str"), false)
557: t.CheckEquals(once.FirstTimeSlice("str", "str2"), true)
558: t.CheckEquals(once.FirstTimeSlice("str", "str2"), false)
1.32 rillig 559:
1.35 rillig 560: t.CheckOutputLines(
561: "FirstTime: str",
562: "FirstTime: str, str2")
1.32 rillig 563: }
564:
1.35 rillig 565: func (s *Suite) Test_Scope__no_tracing(c *check.C) {
1.13 rillig 566: t := s.Init(c)
567:
1.35 rillig 568: scope := NewScope()
569: scope.Define("VAR.param", t.NewMkLine("fname.mk", 3, "VAR.param=\tvalue"))
570: t.DisableTracing()
1.13 rillig 571:
1.35 rillig 572: t.CheckEquals(scope.IsDefinedSimilar("VAR.param"), true)
573: t.CheckEquals(scope.IsDefinedSimilar("VAR.other"), true)
574: t.CheckEquals(scope.IsDefinedSimilar("OTHER"), false)
575: }
1.13 rillig 576:
1.35 rillig 577: func (s *Suite) Test_Scope__commented_varassign(c *check.C) {
578: t := s.Init(c)
1.13 rillig 579:
1.35 rillig 580: mkline := t.NewMkLine("mk/defaults/mk.conf", 3, "#VAR=default")
581: scope := NewScope()
582: scope.Define("VAR", mkline)
1.13 rillig 583:
1.35 rillig 584: t.CheckEquals(scope.IsDefined("VAR"), false)
585: t.Check(scope.FirstDefinition("VAR"), check.IsNil)
586: t.Check(scope.LastDefinition("VAR"), check.IsNil)
1.13 rillig 587:
1.35 rillig 588: t.CheckEquals(scope.Mentioned("VAR"), mkline)
589: t.CheckEquals(scope.Commented("VAR"), mkline)
1.13 rillig 590:
1.35 rillig 591: value, found := scope.LastValueFound("VAR")
592: t.CheckEquals(value, "")
593: t.CheckEquals(found, false)
1.13 rillig 594: }
595:
1.23 rillig 596: func (s *Suite) Test_Scope_Define(c *check.C) {
597: t := s.Init(c)
598:
599: scope := NewScope()
600: scope.Define("BUILD_DIRS", t.NewMkLine("file.mk", 121, "BUILD_DIRS=\tone two three"))
601:
1.32 rillig 602: t.CheckEquals(scope.LastValue("BUILD_DIRS"), "one two three")
1.23 rillig 603:
604: scope.Define("BUILD_DIRS", t.NewMkLine("file.mk", 123, "BUILD_DIRS+=\tfour"))
605:
1.32 rillig 606: t.CheckEquals(scope.LastValue("BUILD_DIRS"), "one two three four")
1.23 rillig 607:
608: // Later default assignments do not have an effect.
609: scope.Define("BUILD_DIRS", t.NewMkLine("file.mk", 123, "BUILD_DIRS?=\tdefault"))
610:
1.32 rillig 611: t.CheckEquals(scope.LastValue("BUILD_DIRS"), "one two three four")
1.23 rillig 612: }
613:
1.35 rillig 614: func (s *Suite) Test_Scope_Mentioned(c *check.C) {
1.13 rillig 615: t := s.Init(c)
616:
1.35 rillig 617: assigned := t.NewMkLine("filename.mk", 3, "VAR=\tvalue")
618: commented := t.NewMkLine("filename.mk", 4, "#COMMENTED=\tvalue")
619: documented := t.NewMkLine("filename.mk", 5, "# DOCUMENTED is a variable.")
620:
1.13 rillig 621: scope := NewScope()
1.35 rillig 622: scope.Define("VAR", assigned)
623: scope.Define("COMMENTED", commented)
624: scope.Define("DOCUMENTED", documented)
1.13 rillig 625:
1.35 rillig 626: t.CheckEquals(scope.Mentioned("VAR"), assigned)
627: t.CheckEquals(scope.Mentioned("COMMENTED"), commented)
628: t.CheckEquals(scope.Mentioned("DOCUMENTED"), documented)
629: t.Check(scope.Mentioned("UNKNOWN"), check.IsNil)
1.13 rillig 630: }
631:
1.35 rillig 632: func (s *Suite) Test_Scope_IsDefined(c *check.C) {
1.13 rillig 633: t := s.Init(c)
634:
635: scope := NewScope()
1.35 rillig 636: scope.Define("VAR.param", t.NewMkLine("file.mk", 1, "VAR.param=value"))
637:
638: t.CheckEquals(scope.IsDefined("VAR.param"), true)
639: t.CheckEquals(scope.IsDefined("VAR.other"), false)
640: t.CheckEquals(scope.IsDefined("VARIABLE.*"), false)
1.13 rillig 641:
1.35 rillig 642: t.CheckEquals(scope.IsDefinedSimilar("VAR.param"), true)
643: t.CheckEquals(scope.IsDefinedSimilar("VAR.other"), true)
644: t.CheckEquals(scope.IsDefinedSimilar("VARIABLE.*"), false)
1.13 rillig 645: }
646:
1.35 rillig 647: func (s *Suite) Test_Scope_IsUsed(c *check.C) {
1.13 rillig 648: t := s.Init(c)
649:
1.35 rillig 650: scope := NewScope()
651: mkline := t.NewMkLine("file.mk", 1, "\techo ${VAR.param}")
652: scope.Use("VAR.param", mkline, VucRunTime)
1.13 rillig 653:
1.35 rillig 654: t.CheckEquals(scope.IsUsed("VAR.param"), true)
655: t.CheckEquals(scope.IsUsed("VAR.other"), false)
656: t.CheckEquals(scope.IsUsed("VARIABLE.*"), false)
657:
658: t.CheckEquals(scope.IsUsedSimilar("VAR.param"), true)
659: t.CheckEquals(scope.IsUsedSimilar("VAR.other"), true)
660: t.CheckEquals(scope.IsUsedSimilar("VARIABLE.*"), false)
1.13 rillig 661: }
662:
1.21 rillig 663: func (s *Suite) Test_Scope_FirstDefinition(c *check.C) {
664: t := s.Init(c)
665:
666: mkline1 := t.NewMkLine("fname.mk", 3, "VAR=\tvalue")
1.23 rillig 667: mkline2 := t.NewMkLine("fname.mk", 3, ".if ${SNEAKY::=value}")
1.21 rillig 668:
669: scope := NewScope()
670: scope.Define("VAR", mkline1)
671: scope.Define("SNEAKY", mkline2)
672:
1.32 rillig 673: t.CheckEquals(scope.FirstDefinition("VAR"), mkline1)
1.21 rillig 674:
675: // This call returns nil because it's not a variable assignment
676: // and the calling code typically assumes a variable definition.
677: // These sneaky variables with implicit definition are an edge
678: // case that only few people actually know. It's better that way.
679: t.Check(scope.FirstDefinition("SNEAKY"), check.IsNil)
680: }
681:
1.35 rillig 682: func (s *Suite) Test_Scope_Commented(c *check.C) {
683: t := s.Init(c)
684:
685: assigned := t.NewMkLine("filename.mk", 3, "VAR=\tvalue")
686: commented := t.NewMkLine("filename.mk", 4, "#COMMENTED=\tvalue")
687: documented := t.NewMkLine("filename.mk", 5, "# DOCUMENTED is a variable.")
688:
689: scope := NewScope()
690: scope.Define("VAR", assigned)
691: scope.Define("COMMENTED", commented)
692: scope.Define("DOCUMENTED", documented)
693:
694: t.Check(scope.Commented("VAR"), check.IsNil)
695: t.CheckEquals(scope.Commented("COMMENTED"), commented)
696: t.Check(scope.Commented("DOCUMENTED"), check.IsNil)
697: t.Check(scope.Commented("UNKNOWN"), check.IsNil)
698: }
699:
1.23 rillig 700: func (s *Suite) Test_Scope_LastValue(c *check.C) {
701: t := s.Init(c)
702:
703: mklines := t.NewMkLines("file.mk",
1.31 rillig 704: MkCvsID,
1.23 rillig 705: "VAR=\tfirst",
706: "VAR=\tsecond",
707: ".if 1",
708: "VAR=\tthird (conditional)",
709: ".endif")
710:
711: mklines.Check()
712:
1.32 rillig 713: t.CheckEquals(mklines.vars.LastValue("VAR"), "third (conditional)")
1.23 rillig 714:
715: t.CheckOutputLines(
716: "WARN: file.mk:2: VAR is defined but not used.")
717: }
718:
1.35 rillig 719: func (s *Suite) Test_Scope_DefineAll(c *check.C) {
1.21 rillig 720: t := s.Init(c)
721:
1.35 rillig 722: src := NewScope()
1.21 rillig 723:
1.35 rillig 724: dst := NewScope()
725: dst.DefineAll(src)
1.21 rillig 726:
1.35 rillig 727: c.Check(dst.firstDef, check.HasLen, 0)
728: c.Check(dst.lastDef, check.HasLen, 0)
729: c.Check(dst.used, check.HasLen, 0)
1.28 rillig 730:
1.35 rillig 731: src.Define("VAR", t.NewMkLine("file.mk", 1, "VAR=value"))
732: dst.DefineAll(src)
1.28 rillig 733:
1.35 rillig 734: t.CheckEquals(dst.IsDefined("VAR"), true)
1.28 rillig 735: }
736:
1.13 rillig 737: func (s *Suite) Test_naturalLess(c *check.C) {
1.32 rillig 738: t := s.Init(c)
1.21 rillig 739:
1.32 rillig 740: var elements = []string{
741: "",
742: // Numbers are always considered smaller than other characters.
743: "0", "000", "0000", "5", "7", "00011", "12", "00012", "000111",
744: "!", "a", "a0", "a ", "aa", "ab", "b"}
1.21 rillig 745:
1.32 rillig 746: test := func(i int, ie string, j int, je string) {
747: actual := naturalLess(ie, je)
748: expected := i < j
749: if actual != expected {
750: t.CheckDeepEquals(
751: []interface{}{i, ie, j, je, actual},
752: []interface{}{i, ie, j, je, expected})
753: }
754: }
1.21 rillig 755:
1.32 rillig 756: for i, ie := range elements {
757: for j, je := range elements {
758: test(i, ie, j, je)
759: }
760: }
1.14 rillig 761: }
762:
763: func (s *Suite) Test_FileCache(c *check.C) {
764: t := s.Init(c)
765:
1.18 rillig 766: t.EnableTracingToLog()
767:
1.14 rillig 768: cache := NewFileCache(3)
769:
770: lines := t.NewLines("Makefile",
1.31 rillig 771: MkCvsID,
1.14 rillig 772: "# line 2")
773:
774: c.Check(cache.Get("Makefile", 0), check.IsNil)
1.32 rillig 775: t.CheckEquals(cache.hits, 0)
776: t.CheckEquals(cache.misses, 1)
1.14 rillig 777:
778: cache.Put("Makefile", 0, lines)
779: c.Check(cache.Get("Makefile", MustSucceed|LogErrors), check.IsNil) // Wrong LoadOptions.
780:
781: linesFromCache := cache.Get("Makefile", 0)
1.36 rillig 782: t.CheckEquals(linesFromCache.Filename, NewPath("Makefile"))
1.17 rillig 783: c.Check(linesFromCache.Lines, check.HasLen, 2)
1.36 rillig 784: t.CheckEquals(linesFromCache.Lines[0].Filename, NewPath("Makefile"))
1.14 rillig 785:
786: // Cache keys are normalized using path.Clean.
787: linesFromCache2 := cache.Get("./Makefile", 0)
1.36 rillig 788: t.CheckEquals(linesFromCache2.Filename, NewPath("./Makefile"))
1.17 rillig 789: c.Check(linesFromCache2.Lines, check.HasLen, 2)
1.36 rillig 790: t.CheckEquals(linesFromCache2.Lines[0].Filename, NewPath("./Makefile"))
1.14 rillig 791:
792: cache.Put("file1.mk", 0, lines)
793: cache.Put("file2.mk", 0, lines)
794:
795: // Now the cache is full. All three entries can be retrieved.
796: c.Check(cache.Get("Makefile", 0), check.NotNil)
797: c.Check(cache.Get("file1.mk", 0), check.NotNil)
798: c.Check(cache.Get("file2.mk", 0), check.NotNil)
799:
800: // Adding another entry removes all entries with minimum count,
801: // which currently are file1.mk and file2.mk.
802: // Makefile is still in the cache because it was accessed once.
803: cache.Put("file3.mk", 0, lines)
804:
805: c.Check(cache.Get("Makefile", 0), check.NotNil)
806: c.Check(cache.Get("file1.mk", 0), check.IsNil)
807: c.Check(cache.Get("file2.mk", 0), check.IsNil)
808: c.Check(cache.Get("file3.mk", 0), check.NotNil)
809:
810: cache.Evict("Makefile")
811:
812: c.Check(cache.Get("Makefile", 0), check.IsNil)
813: c.Check(cache.table, check.HasLen, 1)
814: c.Check(cache.mapping, check.HasLen, 1)
1.32 rillig 815: t.CheckEquals(cache.hits, 7)
816: t.CheckEquals(cache.misses, 5)
1.17 rillig 817:
818: t.CheckOutputLines(
1.18 rillig 819: "TRACE: FileCache \"Makefile\" with count 4.",
820: "TRACE: FileCache \"file1.mk\" with count 2.",
821: "TRACE: FileCache \"file2.mk\" with count 2.",
822: "TRACE: FileCache.Evict \"file2.mk\" with count 2.",
823: "TRACE: FileCache.Evict \"file1.mk\" with count 2.",
824: "TRACE: FileCache.Halve \"Makefile\" with count 4.")
1.17 rillig 825: }
826:
1.29 rillig 827: func (s *Suite) Test_FileCache_removeOldEntries__branch_coverage(c *check.C) {
828: t := s.Init(c)
829:
830: t.EnableTracingToLog()
831: G.Testing = false
832:
833: lines := t.NewLines("filename.mk",
1.31 rillig 834: MkCvsID)
1.29 rillig 835: cache := NewFileCache(3)
836: cache.Put("filename1.mk", 0, lines)
837: cache.Put("filename2.mk", 0, lines)
838: cache.Get("filename2.mk", 0)
839: cache.Get("filename2.mk", 0)
840: cache.Put("filename3.mk", 0, lines)
841: cache.Put("filename4.mk", 0, lines)
842:
843: t.CheckOutputLines(
844: "TRACE: FileCache.Evict \"filename3.mk\" with count 1.",
845: "TRACE: FileCache.Evict \"filename1.mk\" with count 1.",
846: "TRACE: FileCache.Halve \"filename2.mk\" with count 3.")
847: }
848:
1.30 rillig 849: func (s *Suite) Test_FileCache_removeOldEntries__no_tracing(c *check.C) {
850: t := s.Init(c)
851:
852: t.DisableTracing()
853:
854: lines := t.NewLines("filename.mk",
1.31 rillig 855: MkCvsID)
1.30 rillig 856: cache := NewFileCache(3)
857: cache.Put("filename1.mk", 0, lines)
858: cache.Put("filename2.mk", 0, lines)
859: cache.Get("filename2.mk", 0)
860: cache.Get("filename2.mk", 0)
861: cache.Put("filename3.mk", 0, lines)
862: cache.Put("filename4.mk", 0, lines)
863:
864: t.CheckOutputEmpty()
865: }
866:
867: // Covers the newLen > 0 condition.
868: func (s *Suite) Test_FileCache_removeOldEntries__zero_capacity(c *check.C) {
869: t := s.Init(c)
870:
871: lines := t.NewLines("filename.mk",
1.31 rillig 872: MkCvsID)
1.30 rillig 873: cache := NewFileCache(1)
874: cache.Put("filename1.mk", 0, lines)
875:
876: // This call removes all existing entries from the cache,
877: // as the cache's capacity is only 1.
878: cache.Put("filename2.mk", 0, lines)
879: }
880:
881: func (s *Suite) Test_FileCache_Evict__sort(c *check.C) {
882: t := s.Init(c)
883:
884: lines := t.NewLines("filename.mk",
1.31 rillig 885: MkCvsID)
1.30 rillig 886: cache := NewFileCache(10)
887: cache.Put("filename0.mk", 0, lines)
888: cache.Put("filename1.mk", 0, lines)
889: cache.Put("filename2.mk", 0, lines)
890: cache.Put("filename3.mk", 0, lines)
891: cache.Put("filename4.mk", 0, lines)
892: cache.Put("filename5.mk", 0, lines)
893: cache.Put("filename6.mk", 0, lines)
894: cache.Put("filename7.mk", 0, lines)
895: cache.Put("filename8.mk", 0, lines)
896: cache.Put("filename9.mk", 0, lines)
897:
898: cache.Evict("filename5.mk")
899:
900: t.Check(cache.table, check.HasLen, 9)
901: t.Check(cache.Get("filename5.mk", 0), check.IsNil)
902: t.Check(cache.Get("filename6.mk", 0), check.NotNil)
903: }
904:
1.34 rillig 905: func (s *Suite) Test_bmakeHelp(c *check.C) {
1.32 rillig 906: t := s.Init(c)
907:
1.34 rillig 908: t.CheckEquals(bmakeHelp("subst"), confMake+" help topic=subst")
1.13 rillig 909: }
1.18 rillig 910:
911: func (s *Suite) Test_wrap(c *check.C) {
1.32 rillig 912: t := s.Init(c)
1.18 rillig 913:
914: wrapped := wrap(20,
915: "See the pkgsrc guide, section \"Package components, Makefile\":",
916: "https://www.NetBSD.org/doc/pkgsrc/pkgsrc.html#components.Makefile.",
917: "",
918: "For more information, ask on the tech-pkg@NetBSD.org mailing list.",
919: "",
920: "\tpreformatted line 1",
921: "\tpreformatted line 2",
922: "",
923: " intentionally indented",
924: "* itemization",
925: "",
926: "Normal",
927: "text",
928: "continues",
929: "here",
930: "with",
931: "linebreaks.",
932: "",
1.22 rillig 933: "Sentence one. Sentence two.",
934: "",
935: "A\tB\tC\tD",
936: "E\tveryVeryVeryVeryVeryVeryVeryVeryLong")
1.18 rillig 937:
938: expected := []string{
939: "See the pkgsrc",
940: "guide, section",
941: "\"Package components,",
942: "Makefile\":",
943: "https://www.NetBSD.org/doc/pkgsrc/pkgsrc.html#components.Makefile.",
944: "",
945: "For more",
946: "information, ask on",
947: "the",
948: "tech-pkg@NetBSD.org",
949: "mailing list.",
950: "",
951: "\tpreformatted line 1",
952: "\tpreformatted line 2",
953: "",
954: " intentionally indented",
955: "* itemization",
956: "",
957: "Normal text",
958: "continues here with",
959: "linebreaks.",
960: "",
961: "Sentence one.",
1.22 rillig 962: "Sentence two.",
963: "",
964: "A\tB\tC\tD E",
965: "veryVeryVeryVeryVeryVeryVeryVeryLong"}
1.18 rillig 966:
1.32 rillig 967: t.CheckDeepEquals(wrapped, expected)
1.18 rillig 968: }
969:
970: func (s *Suite) Test_escapePrintable(c *check.C) {
1.32 rillig 971: t := s.Init(c)
972:
973: t.CheckEquals(escapePrintable(""), "")
974: t.CheckEquals(escapePrintable("ASCII only~\n\t"), "ASCII only~\n\t")
975: t.CheckEquals(escapePrintable("Beep \u0007 control \u001F"), "Beep <U+0007> control <U+001F>")
976: t.CheckEquals(escapePrintable("Bad \xFF character"), "Bad <0xFF> character")
977: t.CheckEquals(escapePrintable("Unicode \uFFFD replacement"), "Unicode <U+FFFD> replacement")
1.18 rillig 978: }
1.22 rillig 979:
980: func (s *Suite) Test_stringSliceLess(c *check.C) {
1.32 rillig 981: t := s.Init(c)
982:
1.22 rillig 983: var elements = [][][]string{
984: {nil, {}},
985: {{"a"}},
986: {{"a", "a"}},
987: {{"a", "b"}},
988: {{"b"}},
989: {{"b", "a"}}}
990:
991: test := func(i int, iElement []string, j int, jElement []string) {
992: actual := stringSliceLess(iElement, jElement)
993: expected := i < j
994: if actual != expected {
1.32 rillig 995: t.CheckDeepEquals(
1.22 rillig 996: []interface{}{i, iElement, j, jElement, actual},
997: []interface{}{i, iElement, j, jElement, expected})
998: }
999: }
1000:
1001: for i, iElements := range elements {
1002: for j, jElements := range elements {
1003: for _, iElement := range iElements {
1004: for _, jElement := range jElements {
1005: test(i, iElement, j, jElement)
1006: }
1007: }
1008: }
1009: }
1010: }
1011:
1012: func (s *Suite) Test_joinSkipEmpty(c *check.C) {
1013: t := s.Init(c)
1014:
1.32 rillig 1015: t.CheckDeepEquals(
1.22 rillig 1016: joinSkipEmpty(", ", "", "one", "", "", "two", "", "three"),
1017: "one, two, three")
1018: }
1019:
1020: func (s *Suite) Test_joinSkipEmptyCambridge(c *check.C) {
1021: t := s.Init(c)
1022:
1.32 rillig 1023: t.CheckDeepEquals(
1.22 rillig 1024: joinSkipEmptyCambridge("and", "", "one", "", "", "two", "", "three"),
1025: "one, two and three")
1026:
1.32 rillig 1027: t.CheckDeepEquals(
1.22 rillig 1028: joinSkipEmptyCambridge("and", "", "one", "", ""),
1029: "one")
1030: }
1031:
1032: func (s *Suite) Test_joinSkipEmptyOxford(c *check.C) {
1033: t := s.Init(c)
1034:
1.32 rillig 1035: t.CheckDeepEquals(
1.22 rillig 1036: joinSkipEmptyOxford("and", "", "one", "", "", "two", "", "three"),
1037: "one, two, and three")
1038: }
1039:
1.31 rillig 1040: func (s *Suite) Test_newPathMatcher(c *check.C) {
1041: t := s.Init(c)
1042:
1043: test := func(pattern string, matchType pathMatchType, matchPattern string) {
1.32 rillig 1044: t.CheckEquals(*newPathMatcher(pattern), pathMatcher{matchType, matchPattern, pattern})
1.31 rillig 1045: }
1046:
1047: testPanic := func(pattern string) {
1048: t.ExpectPanic(
1049: func() { _ = newPathMatcher(pattern) },
1050: "Pkglint internal error")
1051: }
1052:
1053: testPanic("*.[0123456]")
1054: testPanic("file.???")
1055: testPanic("*.???")
1056: test("", pmExact, "")
1057: test("exact", pmExact, "exact")
1058: test("*.mk", pmSuffix, ".mk")
1059: test("Makefile.*", pmPrefix, "Makefile.")
1060: testPanic("*.*")
1061: testPanic("**")
1062: testPanic("a*b")
1063: testPanic("[")
1064: testPanic("malformed[")
1065: }
1066:
1067: func (s *Suite) Test_pathMatcher_matches(c *check.C) {
1.32 rillig 1068: t := s.Init(c)
1.31 rillig 1069:
1070: test := func(pattern string, subject string, expected bool) {
1071: matcher := newPathMatcher(pattern)
1.32 rillig 1072: t.CheckEquals(matcher.matches(subject), expected)
1.31 rillig 1073: }
1074:
1075: test("", "", true)
1076: test("", "any", false)
1077: test("exact", "exact", true)
1078: test("exact", "different", false)
1079:
1080: test("*.mk", "filename.mk", true)
1081: test("*.mk", "filename.txt", false)
1082: test("*.mk", "filename.mkx", false)
1083: test("*.mk", ".mk", true)
1084:
1085: test("Makefile.*", "Makefile", false)
1086: test("Makefile.*", "Makefile.", true)
1087: test("Makefile.*", "Makefile.txt", true)
1088: test("Makefile.*", "makefile.txt", false)
1089: }
1090:
1.22 rillig 1091: func (s *Suite) Test_StringInterner(c *check.C) {
1092: t := s.Init(c)
1093:
1094: si := NewStringInterner()
1095:
1.32 rillig 1096: t.CheckEquals(si.Intern(""), "")
1097: t.CheckEquals(si.Intern("Hello"), "Hello")
1098: t.CheckEquals(si.Intern("Hello, world"), "Hello, world")
1099: t.CheckEquals(si.Intern("Hello, world"[0:5]), "Hello")
1.22 rillig 1100: }
1.34 rillig 1101:
1102: func (s *Suite) Test_shquote(c *check.C) {
1103: t := s.Init(c)
1104:
1105: test := func(in, out string) {
1106: t.CheckEquals(shquote(in), out)
1107: }
1108:
1109: test("", "''")
1110: test("'", "''\\'''")
1111: test("simple", "simple")
1112: test("~", "'~'")
1113: }
CVSweb <webmaster@jp.NetBSD.org>