Annotation of pkgsrc/pkgtools/pkglint/files/shell_test.go, Revision 1.58
1.35 rillig 1: package pkglint
1.1 rillig 2:
3: import (
1.19 rillig 4: "gopkg.in/check.v1"
1.34 rillig 5: "strings"
1.1 rillig 6: )
7:
1.56 rillig 8: func (s *Suite) Test_SimpleCommandChecker_handleForbiddenCommand(c *check.C) {
1.20 rillig 9: t := s.Init(c)
10:
1.56 rillig 11: mklines := t.NewMkLines("Makefile",
12: MkCvsID,
13: "",
14: "\t${RUN} mktexlsr; texconfig")
1.1 rillig 15:
1.56 rillig 16: mklines.Check()
1.1 rillig 17:
1.20 rillig 18: t.CheckOutputLines(
1.56 rillig 19: "ERROR: Makefile:3: \"mktexlsr\" must not be used in Makefiles.",
20: "ERROR: Makefile:3: \"texconfig\" must not be used in Makefiles.")
1.9 rillig 21: }
22:
1.56 rillig 23: func (s *Suite) Test_SimpleCommandChecker_handleCommandVariable(c *check.C) {
1.50 rillig 24: t := s.Init(c)
25:
1.56 rillig 26: t.SetUpTool("perl", "PERL5", AtRunTime)
27: t.SetUpTool("perl6", "PERL6", Nowhere)
28: mklines := t.NewMkLines("Makefile",
29: MkCvsID,
30: "",
31: "PERL5_VARS_CMD=\t${PERL5:Q}",
32: "PERL5_VARS_CMD=\t${PERL6:Q}",
33: "",
34: "pre-configure:",
35: "\t${PERL5_VARS_CMD} -e 'print 12345'")
1.1 rillig 36:
1.56 rillig 37: mklines.Check()
1.1 rillig 38:
1.56 rillig 39: // FIXME: In PERL5:Q and PERL6:Q, the :Q is wrong.
40: t.CheckOutputLines(
41: "WARN: Makefile:4: The \"${PERL6:Q}\" tool is used but not added to USE_TOOLS.")
1.9 rillig 42: }
43:
1.56 rillig 44: func (s *Suite) Test_SimpleCommandChecker_handleCommandVariable__parameterized(c *check.C) {
1.50 rillig 45: t := s.Init(c)
46:
1.56 rillig 47: t.SetUpPackage("category/package")
48: G.Pkg = NewPackage(t.File("category/package"))
49: t.FinishSetUp()
1.9 rillig 50:
1.56 rillig 51: mklines := t.NewMkLines("Makefile",
52: MkCvsID,
53: "",
54: "MY_TOOL.i386=\t${PREFIX}/bin/tool-i386",
55: "MY_TOOL.x86_64=\t${PREFIX}/bin/tool-x86_64",
56: "",
57: "pre-configure:",
58: "\t${MY_TOOL.amd64} -e 'print 12345'",
59: "\t${UNKNOWN_TOOL}")
1.9 rillig 60:
1.56 rillig 61: mklines.Check()
1.50 rillig 62:
1.56 rillig 63: t.CheckOutputLines(
64: "WARN: Makefile:8: Unknown shell command \"${UNKNOWN_TOOL}\".",
65: "WARN: Makefile:8: UNKNOWN_TOOL is used but not defined.")
1.9 rillig 66: }
67:
1.56 rillig 68: func (s *Suite) Test_SimpleCommandChecker_handleCommandVariable__followed_by_literal(c *check.C) {
1.50 rillig 69: t := s.Init(c)
70:
1.56 rillig 71: t.SetUpPackage("category/package")
72: G.Pkg = NewPackage(t.File("category/package"))
73: t.FinishSetUp()
1.30 rillig 74:
1.56 rillig 75: mklines := t.NewMkLines("Makefile",
76: MkCvsID,
77: "",
78: "QTDIR=\t${PREFIX}",
79: "",
80: "pre-configure:",
81: "\t${QTDIR}/bin/release")
1.30 rillig 82:
1.56 rillig 83: mklines.Check()
1.50 rillig 84:
1.56 rillig 85: t.CheckOutputEmpty()
1.30 rillig 86: }
87:
1.56 rillig 88: // The package Makefile and other .mk files in a package directory
89: // may use any shell commands defined by any included files.
90: // But only if the package is checked as a whole.
91: //
92: // On the contrary, when pkglint checks a single .mk file, these
93: // commands are not guaranteed to be defined, not even when the
94: // .mk file includes the file defining the command.
95: // FIXME: This paragraph sounds wrong. All commands from included files should be valid.
96: //
97: // The PYTHON_BIN variable below must not be called *_CMD, or another code path is taken.
98: func (s *Suite) Test_SimpleCommandChecker_handleCommandVariable__from_package(c *check.C) {
1.30 rillig 99: t := s.Init(c)
100:
1.56 rillig 101: pkg := t.SetUpPackage("category/package",
102: "post-install:",
103: "\t${PYTHON_BIN}",
104: "",
105: ".include \"extra.mk\"")
106: t.CreateFileLines("category/package/extra.mk",
107: MkCvsID,
108: "PYTHON_BIN=\tmy_cmd")
109: t.FinishSetUp()
1.30 rillig 110:
1.56 rillig 111: G.Check(pkg)
1.30 rillig 112:
113: t.CheckOutputEmpty()
114: }
115:
1.56 rillig 116: func (s *Suite) Test_SimpleCommandChecker_checkRegexReplace(c *check.C) {
1.50 rillig 117: t := s.Init(c)
118:
1.56 rillig 119: test := func(cmd string, diagnostics ...string) {
120: t.SetUpTool("pax", "PAX", AtRunTime)
121: t.SetUpTool("sed", "SED", AtRunTime)
122: mklines := t.NewMkLines("Makefile",
123: MkCvsID,
124: "pre-configure:",
125: "\t"+cmd)
1.9 rillig 126:
1.56 rillig 127: mklines.Check()
1.9 rillig 128:
1.56 rillig 129: t.CheckOutput(diagnostics)
130: }
1.50 rillig 131:
1.56 rillig 132: test("${PAX} -s s,.*,, src dst",
133: "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.")
1.34 rillig 134:
1.56 rillig 135: test("pax -s s,.*,, src dst",
136: "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.")
1.34 rillig 137:
1.56 rillig 138: test("${SED} -e s,.*,, src dst",
139: "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.")
1.50 rillig 140:
1.56 rillig 141: test("sed -e s,.*,, src dst",
142: "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.")
1.9 rillig 143:
1.56 rillig 144: // The * is properly enclosed in quotes.
145: test("sed -e 's,.*,,' -e \"s,-*,,\"",
146: nil...)
1.9 rillig 147:
1.56 rillig 148: // The * is properly escaped.
149: test("sed -e s,.\\*,,",
150: nil...)
1.50 rillig 151:
1.56 rillig 152: test("pax -s s,\\.orig,, src dst",
153: nil...)
1.10 rillig 154:
1.56 rillig 155: test("sed -e s,a,b,g src dst",
156: nil...)
1.10 rillig 157:
1.56 rillig 158: // TODO: Merge the code with BtSedCommands.
1.10 rillig 159:
1.56 rillig 160: // TODO: Finally, remove the G.Testing from the main code.
161: // Then, remove this test case.
162: G.Testing = false
163: test("sed -e s,.*,match,",
164: nil...)
165: G.Testing = true
1.10 rillig 166: }
167:
1.56 rillig 168: func (s *Suite) Test_SimpleCommandChecker_checkAutoMkdirs(c *check.C) {
1.20 rillig 169: t := s.Init(c)
170:
1.37 rillig 171: t.SetUpVartypes()
1.56 rillig 172: // TODO: Check whether these tools are actually necessary for this test.
1.37 rillig 173: t.SetUpTool("awk", "AWK", AtRunTime)
174: t.SetUpTool("cp", "CP", AtRunTime)
175: t.SetUpTool("echo", "", AtRunTime)
176: t.SetUpTool("mkdir", "MKDIR", AtRunTime) // This is actually "mkdir -p".
177: t.SetUpTool("unzip", "UNZIP_CMD", AtRunTime)
1.3 rillig 178:
1.42 rillig 179: test := func(shellCommand string, diagnostics ...string) {
1.43 rillig 180: mklines := t.NewMkLines("filename.mk",
1.19 rillig 181: "\t"+shellCommand)
1.43 rillig 182: ck := NewShellLineChecker(mklines, mklines.mklines[0])
1.19 rillig 183:
1.49 rillig 184: mklines.ForEach(func(mkline *MkLine) {
1.42 rillig 185: ck.CheckShellCommandLine(ck.mkline.ShellCommand())
1.30 rillig 186: })
1.42 rillig 187:
188: t.CheckOutput(diagnostics)
1.19 rillig 189: }
190:
1.56 rillig 191: // AUTO_MKDIRS applies only when installing directories.
192: test("${RUN} ${INSTALL} -c ${WRKSRC}/file ${PREFIX}/bin/",
1.42 rillig 193: nil...)
1.3 rillig 194:
1.56 rillig 195: // TODO: Warn that ${INSTALL} -d can only handle a single directory.
196: test("${RUN} ${INSTALL} -m 0755 -d ${PREFIX}/first ${PREFIX}/second",
197: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= first\" instead of \"${INSTALL} -d\".",
198: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= second\" instead of \"${INSTALL} -d\".")
1.3 rillig 199:
1.56 rillig 200: G.Pkg = NewPackage(t.File("category/pkgbase"))
201: G.Pkg.Plist.Dirs["share/pkgbase"] = &PlistLine{
202: t.NewLine("PLIST", 123, "share/pkgbase/file"),
203: nil,
204: "share/pkgbase/file"}
1.3 rillig 205:
1.56 rillig 206: // A directory that is found in the PLIST.
207: // TODO: Add a test for using this command inside a conditional;
208: // the note should not appear then.
209: test("${RUN} ${INSTALL_DATA_DIR} share/pkgbase ${PREFIX}/share/pkgbase",
210: "NOTE: filename.mk:1: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= share/pkgbase\" "+
211: "instead of \"${INSTALL_DATA_DIR}\".",
212: "WARN: filename.mk:1: The INSTALL_*_DIR commands can only handle one directory at a time.")
1.53 rillig 213:
1.56 rillig 214: // Directories from .for loops are too dynamic to be replaced with AUTO_MKDIRS.
215: // TODO: Expand simple .for loops.
216: test("${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/${dir}",
217: "WARN: filename.mk:1: dir is used but not defined.")
1.53 rillig 218:
1.56 rillig 219: // A directory that is not found in the PLIST would not be created by AUTO_MKDIRS,
220: // therefore only INSTALLATION_DIRS is suggested.
221: test("${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/share/other",
222: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= share/other\" instead of \"${INSTALL_DATA_DIR}\".")
223: }
1.3 rillig 224:
1.56 rillig 225: func (s *Suite) Test_SimpleCommandChecker_checkAutoMkdirs__redundant(c *check.C) {
226: t := s.Init(c)
1.4 rillig 227:
1.56 rillig 228: t.SetUpPackage("category/package",
229: "AUTO_MKDIRS=\t\tyes",
230: "INSTALLATION_DIRS+=\tshare/redundant",
231: "INSTALLATION_DIRS+=\tnot/redundant ${EGDIR}")
232: t.CreateFileLines("category/package/PLIST",
233: PlistCvsID,
234: "share/redundant/file",
235: "${EGDIR}/file")
236:
237: t.Main("-Wall", "-q", "category/package")
238:
239: t.CheckOutputLines(
240: "NOTE: ~/category/package/Makefile:21: The directory \"share/redundant\" "+
241: "is redundant in INSTALLATION_DIRS.",
242: // The below is not proven to be always correct. It assumes that a
243: // variable in the Makefile has the same value as the corresponding
244: // variable from PLIST_SUBST. Violating this assumption would be
245: // confusing to the pkgsrc developers, therefore it's a safe bet.
246: // A notable counterexample is PKGNAME in PLIST, which corresponds
247: // to PKGNAME_NOREV in the package Makefile.
248: "NOTE: ~/category/package/Makefile:22: The directory \"${EGDIR}\" "+
249: "is redundant in INSTALLATION_DIRS.")
250: }
251:
252: // The AUTO_MKDIRS code in mk/install/install.mk (install-dirs-from-PLIST)
253: // skips conditional directories, as well as directories with placeholders.
254: func (s *Suite) Test_SimpleCommandChecker_checkAutoMkdirs__conditional_PLIST(c *check.C) {
255: t := s.Init(c)
256:
257: t.SetUpPackage("category/package",
258: "LIB_SUBDIR=\tsubdir",
259: "",
260: "do-install:",
261: "\t${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/libexec/always",
262: "\t${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/libexec/conditional",
263: "\t${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/${LIB_SUBDIR}",
264: )
265: t.Chdir("category/package")
266: t.CreateFileLines("PLIST",
267: PlistCvsID,
268: "libexec/always/always",
269: "${LIB_SUBDIR}/file",
270: "${PLIST.cond}libexec/conditional/conditional")
271: t.FinishSetUp()
272:
273: G.checkdirPackage(".")
274:
275: // As libexec/conditional will not be created automatically,
276: // AUTO_MKDIRS must not be suggested in that line.
277: t.CheckOutputLines(
278: "NOTE: Makefile:23: You can use AUTO_MKDIRS=yes "+
279: "or \"INSTALLATION_DIRS+= libexec/always\" "+
280: "instead of \"${INSTALL_DATA_DIR}\".",
281: "NOTE: Makefile:24: You can use "+
282: "\"INSTALLATION_DIRS+= libexec/conditional\" "+
283: "instead of \"${INSTALL_DATA_DIR}\".",
284: "NOTE: Makefile:25: You can use "+
285: "\"INSTALLATION_DIRS+= ${LIB_SUBDIR}\" "+
286: "instead of \"${INSTALL_DATA_DIR}\".")
287: }
288:
289: // This test ensures that the command line options to INSTALL_*_DIR are properly
290: // parsed and do not lead to "can only handle one directory at a time" warnings.
291: func (s *Suite) Test_SimpleCommandChecker_checkInstallMulti(c *check.C) {
292: t := s.Init(c)
293:
294: t.SetUpVartypes()
295: mklines := t.NewMkLines("install.mk",
296: MkCvsID,
297: "",
298: "do-install:",
299: "\t${INSTALL_PROGRAM_DIR} -m 0555 -g ${APACHE_GROUP} -o ${APACHE_USER} \\",
300: "\t\t${DESTDIR}${PREFIX}/lib/apache-modules")
301:
302: mklines.Check()
303:
304: t.CheckOutputLines(
305: "NOTE: install.mk:4--5: You can use \"INSTALLATION_DIRS+= lib/apache-modules\" " +
306: "instead of \"${INSTALL_PROGRAM_DIR}\".")
307: }
308:
309: func (s *Suite) Test_SimpleCommandChecker_checkPaxPe(c *check.C) {
310: t := s.Init(c)
311:
312: t.SetUpVartypes()
313: t.SetUpTool("pax", "PAX", AtRunTime)
314: mklines := t.NewMkLines("Makefile",
315: MkCvsID,
316: "",
317: "do-install:",
318: "\t${RUN} pax -pe ${WRKSRC} ${DESTDIR}${PREFIX}",
319: "\t${RUN} ${PAX} -pe ${WRKSRC} ${DESTDIR}${PREFIX}")
320:
321: mklines.Check()
322:
323: t.CheckOutputLines(
324: "WARN: Makefile:4: Please use the -pp option to pax(1) instead of -pe.",
325: "WARN: Makefile:5: Please use the -pp option to pax(1) instead of -pe.")
326: }
327:
328: func (s *Suite) Test_SimpleCommandChecker_checkEchoN(c *check.C) {
329: t := s.Init(c)
330:
331: t.SetUpTool("echo", "ECHO", AtRunTime)
332: t.SetUpTool("echo -n", "ECHO_N", AtRunTime)
333: mklines := t.NewMkLines("Makefile",
334: MkCvsID,
335: "",
336: "do-install:",
337: "\t${RUN} ${ECHO} -n 'Computing...'",
338: "\t${RUN} ${ECHO_N} 'Computing...'",
339: "\t${RUN} ${ECHO} 'Computing...'")
340:
341: mklines.Check()
342:
343: t.CheckOutputLines(
344: "WARN: Makefile:4: Please use ${ECHO_N} instead of \"echo -n\".")
345: }
346:
347: func (s *Suite) Test_ShellLineChecker__shell_comment_with_line_continuation(c *check.C) {
348: t := s.Init(c)
349:
350: t.SetUpTool("echo", "", AtRunTime)
351:
352: test := func(lines ...string) {
353: i := 0
354: for ; i < len(lines) && hasPrefix(lines[i], "\t"); i++ {
355: }
356:
357: mklines := t.SetUpFileMkLines("Makefile",
358: append([]string{MkCvsID, "pre-install:"},
359: lines[:i]...)...)
360:
361: mklines.Check()
362:
363: t.CheckOutput(lines[i:])
364: }
365:
366: // The comment can start at the beginning of a follow-up line.
367: test(
368: "\techo first; \\",
369: "\t# comment at the beginning of a command \\",
370: "\techo \"hello\"",
371:
372: // TODO: Warn that the "echo hello" is commented out.
373: )
374:
375: // The comment can start at the beginning of a simple command.
376: test(
377: "\techo first; # comment at the beginning of a command \\",
378: "\techo \"hello\"",
379:
380: // TODO: Warn that the "echo hello" is commented out.
381: )
382:
383: // The comment can start at a word in the middle of a command.
384: test(
385: // TODO: Warn that the "echo hello" is commented out.
386: "\techo # comment starts inside a command \\",
387: "\techo \"hello\"")
388:
389: // If the comment starts in the last line, there's no further
390: // line that might be commented out accidentally.
391: test(
392: "\techo 'first line'; \\",
393: "\t# comment in last line")
394: }
1.10 rillig 395:
1.56 rillig 396: func (s *Suite) Test_ShellLineChecker_checkConditionalCd(c *check.C) {
397: t := s.Init(c)
1.4 rillig 398:
1.56 rillig 399: t.SetUpTool("ls", "", AtRunTime)
400: t.SetUpTool("printf", "", AtRunTime)
401: t.SetUpTool("wc", "", AtRunTime)
402: mklines := t.NewMkLines("Makefile",
403: MkCvsID,
404: "pre-configure:",
405: "\t${RUN} while cd ..; do printf .; done",
406: "\t${RUN} while cd .. && cd ..; do printf .; done", // Unusual, therefore no warning.
407: "\t${RUN} if cd ..; then printf .; fi",
408: "\t${RUN} ! cd ..",
409: "\t${RUN} if cd ..; printf 'ok\\n'; then printf .; fi",
410: "\t${RUN} if cd .. | wc -l; then printf .; fi", // Unusual, therefore no warning.
411: "\t${RUN} if cd .. && cd ..; then printf .; fi") // Unusual, therefore no warning.
1.7 rillig 412:
1.56 rillig 413: mklines.Check()
1.7 rillig 414:
1.56 rillig 415: t.CheckOutputLines(
416: "ERROR: Makefile:3: The Solaris /bin/sh cannot handle \"cd\" inside conditionals.",
417: "ERROR: Makefile:5: The Solaris /bin/sh cannot handle \"cd\" inside conditionals.",
418: "WARN: Makefile:6: The Solaris /bin/sh does not support negation of shell commands.",
419: "WARN: Makefile:8: The exitcode of \"cd\" at the left of the | operator is ignored.")
420: }
1.7 rillig 421:
1.56 rillig 422: func (s *Suite) Test_ShellLineChecker_checkSetE__simple_commands(c *check.C) {
423: t := s.Init(c)
1.9 rillig 424:
1.56 rillig 425: t.SetUpTool("echo", "", AtRunTime)
426: t.SetUpTool("rm", "", AtRunTime)
427: t.SetUpTool("touch", "", AtRunTime)
428: mklines := t.NewMkLines("Makefile",
429: MkCvsID,
430: "pre-configure:",
431: "\techo 1; echo 2; echo 3",
432: "\techo 1; touch file; rm file",
433: "\techo 1; var=value; echo 3")
1.7 rillig 434:
1.56 rillig 435: mklines.Check()
1.7 rillig 436:
1.56 rillig 437: t.CheckOutputLines(
438: "WARN: Makefile:4: Please switch to \"set -e\" mode before using a semicolon " +
439: "(after \"touch file\") to separate commands.")
440: }
1.7 rillig 441:
1.56 rillig 442: func (s *Suite) Test_ShellLineChecker_checkSetE__compound_commands(c *check.C) {
443: t := s.Init(c)
1.31 rillig 444:
1.56 rillig 445: t.SetUpTool("echo", "", AtRunTime)
446: t.SetUpTool("touch", "", AtRunTime)
447: mklines := t.NewMkLines("Makefile",
448: MkCvsID,
449: "pre-configure:",
450: "\ttouch file; for f in file; do echo \"$$f\"; done",
451: "\tfor f in file; do echo \"$$f\"; done; touch file",
452: "\ttouch 1; touch 2; touch 3; touch 4")
1.31 rillig 453:
1.56 rillig 454: mklines.Check()
1.7 rillig 455:
1.56 rillig 456: t.CheckOutputLines(
457: "WARN: Makefile:3: Please switch to \"set -e\" mode before using a semicolon "+
458: "(after \"touch file\") to separate commands.",
459: "WARN: Makefile:5: Please switch to \"set -e\" mode before using a semicolon "+
460: "(after \"touch 1\") to separate commands.")
461: }
1.18 rillig 462:
1.56 rillig 463: func (s *Suite) Test_ShellLineChecker_checkSetE__no_tracing(c *check.C) {
464: t := s.Init(c)
1.10 rillig 465:
1.56 rillig 466: t.SetUpTool("touch", "", AtRunTime)
467: mklines := t.NewMkLines("Makefile",
468: MkCvsID,
469: "pre-configure:",
470: "\ttouch 1; touch 2")
471: t.DisableTracing()
1.18 rillig 472:
1.56 rillig 473: mklines.Check()
1.18 rillig 474:
1.56 rillig 475: t.CheckOutputLines(
476: "WARN: Makefile:3: Please switch to \"set -e\" mode before using a semicolon " +
477: "(after \"touch 1\") to separate commands.")
1.1 rillig 478: }
479:
1.56 rillig 480: func (s *Suite) Test_ShellLineChecker_canFail(c *check.C) {
1.24 rillig 481: t := s.Init(c)
482:
1.56 rillig 483: t.SetUpVartypes()
484: t.SetUpTool("basename", "", AtRunTime)
485: t.SetUpTool("dirname", "", AtRunTime)
486: t.SetUpTool("echo", "", AtRunTime)
487: t.SetUpTool("env", "", AtRunTime)
488: t.SetUpTool("grep", "GREP", AtRunTime)
489: t.SetUpTool("sed", "", AtRunTime)
490: t.SetUpTool("touch", "", AtRunTime)
491: t.SetUpTool("tr", "tr", AtRunTime)
492: t.SetUpTool("true", "TRUE", AtRunTime)
493:
494: test := func(cmd string, diagnostics ...string) {
495: mklines := t.NewMkLines("Makefile",
496: MkCvsID,
497: "pre-configure:",
498: "\t"+cmd+" ; echo 'done.'")
499:
500: mklines.Check()
501:
502: t.CheckOutput(diagnostics)
503: }
504:
505: test("socklen=`${GREP} 'expr' ${WRKSRC}/config.h`",
506: "WARN: Makefile:3: Please switch to \"set -e\" mode before using a semicolon "+
507: "(after \"socklen=`${GREP} 'expr' ${WRKSRC}/config.h`\") to separate commands.")
508:
509: test("socklen=`${GREP} 'expr' ${WRKSRC}/config.h || ${TRUE}`",
510: nil...)
1.24 rillig 511:
1.56 rillig 512: test("socklen=$$(expr 16)",
513: "WARN: Makefile:3: Invoking subshells via $(...) is not portable enough.",
514: "WARN: Makefile:3: Please switch to \"set -e\" mode before using a semicolon "+
515: "(after \"socklen=$$(expr 16)\") to separate commands.")
1.24 rillig 516:
1.56 rillig 517: test("socklen=$$(expr 16 || true)",
518: "WARN: Makefile:3: Invoking subshells via $(...) is not portable enough.")
1.24 rillig 519:
1.56 rillig 520: test("socklen=$$(expr 16 || ${TRUE})",
521: "WARN: Makefile:3: Invoking subshells via $(...) is not portable enough.")
1.24 rillig 522:
1.56 rillig 523: test("${ECHO_MSG} \"Message\"",
524: nil...)
1.24 rillig 525:
1.56 rillig 526: test("${FAIL_MSG} \"Failure\"",
527: "WARN: Makefile:3: Please switch to \"set -e\" mode before using a semicolon "+
528: "(after \"${FAIL_MSG} \\\"Failure\\\"\") to separate commands.")
1.24 rillig 529:
1.56 rillig 530: test("set -x",
531: "WARN: Makefile:3: Please switch to \"set -e\" mode before using a semicolon "+
532: "(after \"set -x\") to separate commands.")
1.24 rillig 533:
1.56 rillig 534: test("echo 'input' | sed -e s,in,out,",
535: nil...)
1.20 rillig 536:
1.56 rillig 537: test("sed -e s,in,out,",
538: nil...)
1.7 rillig 539:
1.56 rillig 540: test("sed s,in,out,",
541: nil...)
1.7 rillig 542:
1.56 rillig 543: test("grep input",
544: nil...)
1.7 rillig 545:
1.56 rillig 546: test("grep pattern file...",
547: "WARN: Makefile:3: Please switch to \"set -e\" mode before using a semicolon "+
548: "(after \"grep pattern file...\") to separate commands.")
1.20 rillig 549:
1.56 rillig 550: test("touch file",
551: "WARN: Makefile:3: Please switch to \"set -e\" mode before using a semicolon "+
552: "(after \"touch file\") to separate commands.")
1.7 rillig 553:
1.56 rillig 554: test("echo 'starting'",
555: nil...)
1.7 rillig 556:
1.56 rillig 557: test("echo 'logging' > log",
558: "WARN: Makefile:3: Please switch to \"set -e\" mode before using a semicolon "+
559: "(after \"echo 'logging'\") to separate commands.")
1.7 rillig 560:
1.56 rillig 561: test("echo 'to stderr' 1>&2",
562: nil...)
1.37 rillig 563:
1.56 rillig 564: test("echo 'hello' | tr -d 'aeiou'",
565: nil...)
1.37 rillig 566:
1.56 rillig 567: test("env | grep '^PATH='",
568: nil...)
1.37 rillig 569:
1.56 rillig 570: test("basename dir/file",
571: nil...)
1.37 rillig 572:
1.56 rillig 573: test("dirname dir/file",
574: nil...)
1.37 rillig 575: }
576:
1.56 rillig 577: func (s *Suite) Test_ShellLineChecker_checkPipeExitcode(c *check.C) {
1.20 rillig 578: t := s.Init(c)
579:
1.37 rillig 580: t.SetUpVartypes()
581: t.SetUpTool("cat", "", AtRunTime)
582: t.SetUpTool("echo", "", AtRunTime)
583: t.SetUpTool("printf", "", AtRunTime)
584: t.SetUpTool("sed", "", AtRunTime)
585: t.SetUpTool("right-side", "", AtRunTime)
1.43 rillig 586: mklines := t.NewMkLines("Makefile",
1.17 rillig 587: "\t echo | right-side",
588: "\t sed s,s,s, | right-side",
589: "\t printf | sed s,s,s, | right-side ",
590: "\t cat | right-side",
591: "\t cat | echo | right-side",
592: "\t echo | cat | right-side",
1.34 rillig 593: "\t sed s,s,s, filename | right-side",
1.31 rillig 594: "\t sed s,s,s < input | right-side",
595: "\t ./unknown | right-side",
596: "\t var=value | right-side",
1.49 rillig 597: "\t if :; then :; fi | right-side",
598: "\t var=`cat file` | right-side")
1.17 rillig 599:
1.43 rillig 600: for _, mkline := range mklines.mklines {
601: ck := NewShellLineChecker(mklines, mkline)
1.42 rillig 602: ck.CheckShellCommandLine(mkline.ShellCommand())
1.17 rillig 603: }
604:
1.20 rillig 605: t.CheckOutputLines(
1.17 rillig 606: "WARN: Makefile:4: The exitcode of \"cat\" at the left of the | operator is ignored.",
607: "WARN: Makefile:5: The exitcode of \"cat\" at the left of the | operator is ignored.",
608: "WARN: Makefile:6: The exitcode of \"cat\" at the left of the | operator is ignored.",
609: "WARN: Makefile:7: The exitcode of \"sed\" at the left of the | operator is ignored.",
1.31 rillig 610: "WARN: Makefile:8: The exitcode of \"sed\" at the left of the | operator is ignored.",
611: "WARN: Makefile:9: The exitcode of \"./unknown\" at the left of the | operator is ignored.",
1.49 rillig 612: "WARN: Makefile:11: The exitcode of the command at the left of the | operator is ignored.",
613: "WARN: Makefile:12: The exitcode of the command at the left of the | operator is ignored.")
1.17 rillig 614: }
615:
1.56 rillig 616: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine(c *check.C) {
1.20 rillig 617: t := s.Init(c)
618:
1.37 rillig 619: t.SetUpVartypes()
1.56 rillig 620: t.SetUpTool("awk", "AWK", AtRunTime)
621: t.SetUpTool("cp", "CP", AtRunTime)
622: t.SetUpTool("echo", "", AtRunTime)
623: t.SetUpTool("mkdir", "MKDIR", AtRunTime) // This is actually "mkdir -p".
624: t.SetUpTool("unzip", "UNZIP_CMD", AtRunTime)
1.6 rillig 625:
1.56 rillig 626: test := func(shellCommand string, diagnostics ...string) {
627: mklines := t.NewMkLines("filename.mk",
628: "\t"+shellCommand)
629: ck := NewShellLineChecker(mklines, mklines.mklines[0])
1.9 rillig 630:
1.56 rillig 631: mklines.ForEach(func(mkline *MkLine) {
632: ck.CheckShellCommandLine(ck.mkline.ShellCommand())
633: })
1.9 rillig 634:
1.56 rillig 635: t.CheckOutput(diagnostics)
636: }
1.6 rillig 637:
1.56 rillig 638: test("@# Comment",
639: nil...)
1.10 rillig 640:
1.56 rillig 641: test("uname=`uname`; echo $$uname; echo; ${PREFIX}/bin/command",
642: "WARN: filename.mk:1: Unknown shell command \"uname\".",
643: "WARN: filename.mk:1: Please switch to \"set -e\" mode "+
644: "before using a semicolon (after \"uname=`uname`\") to separate commands.")
1.10 rillig 645:
1.56 rillig 646: test("echo ${PKGNAME:Q}", // VucQuotPlain
647: "NOTE: filename.mk:1: The :Q modifier isn't necessary for ${PKGNAME} here.")
1.9 rillig 648:
1.56 rillig 649: test("echo \"${CFLAGS:Q}\"", // VucQuotDquot
1.6 rillig 650:
1.56 rillig 651: // ShellLineChecker.checkVaruseToken
652: "WARN: filename.mk:1: The :Q modifier should not be used inside quotes.",
1.20 rillig 653:
1.56 rillig 654: // ShellLineChecker.checkVaruseToken
655: // MkLineChecker.CheckVaruse
656: // MkLineChecker.checkVarUseQuoting
657: "WARN: filename.mk:1: Please use ${CFLAGS:M*:Q} instead of ${CFLAGS:Q} "+
658: "and make sure the variable appears outside of any quoting characters.")
1.6 rillig 659:
1.56 rillig 660: test("echo '${COMMENT:Q}'", // VucQuotSquot
661: "WARN: filename.mk:1: The :Q modifier should not be used inside quotes.",
662: "WARN: filename.mk:1: Please move ${COMMENT:Q} outside of any quoting characters.")
1.6 rillig 663:
1.56 rillig 664: test("echo target=$@ exitcode=$$? '$$' \"\\$$\"",
665: "WARN: filename.mk:1: Please use \"${.TARGET}\" instead of \"$@\".",
666: "WARN: filename.mk:1: The $? shell variable is often not available in \"set -e\" mode.")
1.6 rillig 667:
1.56 rillig 668: test("echo $$@",
669: "WARN: filename.mk:1: The $@ shell variable should only be used in double quotes.")
1.20 rillig 670:
1.56 rillig 671: // No warning about a possibly missed variable name.
672: // This occurs only rarely, and typically as part of a regular expression
673: // where it is used intentionally.
674: test("echo \"$$\"", // As seen by make(1); the shell sees: echo "$"
675: nil...)
1.1 rillig 676:
1.56 rillig 677: test("echo \"\\n\"",
678: nil...)
1.19 rillig 679:
1.56 rillig 680: test("${RUN} for f in *.c; do echo $${f%.c}; done",
681: nil...)
1.9 rillig 682:
1.56 rillig 683: test("${RUN} set +x; echo $${variable+set}",
684: nil...)
1.1 rillig 685:
1.56 rillig 686: // Based on mail/thunderbird/Makefile, rev. 1.159
687: test("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"",
688: "WARN: filename.mk:1: Double quotes inside backticks inside double quotes are error prone.",
689: "WARN: filename.mk:1: The exitcode of \"unzip\" at the left of the | operator is ignored.")
1.37 rillig 690:
1.56 rillig 691: // From mail/thunderbird/Makefile, rev. 1.159
692: test(""+
693: "${RUN} for e in ${XPI_FILES}; do "+
694: " subdir=\"`${UNZIP_CMD} -c \"$$e\" install.rdf | "+
695: ""+"awk '/.../ {print;exit;}'`\" && "+
696: " ${MKDIR} \"${WRKDIR}/extensions/$$subdir\" && "+
697: " cd \"${WRKDIR}/extensions/$$subdir\" && "+
698: " ${UNZIP_CMD} -aqo $$e; "+
699: "done",
700: "WARN: filename.mk:1: XPI_FILES is used but not defined.",
701: "WARN: filename.mk:1: Double quotes inside backticks inside double quotes are error prone.",
702: "WARN: filename.mk:1: The exitcode of \"${UNZIP_CMD}\" at the left of the | operator is ignored.")
1.30 rillig 703:
1.56 rillig 704: // From x11/wxGTK28/Makefile
705: // TODO: Why is TOOLS_PATH.msgfmt not recognized?
706: // At least, the warning should be more specific, mentioning USE_TOOLS.
707: test(""+
708: "set -e; cd ${WRKSRC}/locale; "+
709: "for lang in *.po; do "+
710: " [ \"$${lang}\" = \"wxstd.po\" ] && continue; "+
711: " ${TOOLS_PATH.msgfmt} -c -o \"$${lang%.po}.mo\" \"$${lang}\"; "+
712: "done",
713: "WARN: filename.mk:1: Unknown shell command \"[\".",
714: "WARN: filename.mk:1: Unknown shell command \"${TOOLS_PATH.msgfmt}\".")
1.30 rillig 715:
1.56 rillig 716: test("@cp from to",
717: "WARN: filename.mk:1: The shell command \"cp\" should not be hidden.")
1.1 rillig 718:
1.56 rillig 719: test("-cp from to",
720: "WARN: filename.mk:1: Using a leading \"-\" to suppress errors is deprecated.")
1.7 rillig 721:
1.56 rillig 722: test("-${MKDIR} deeply/nested/subdir",
723: "WARN: filename.mk:1: Using a leading \"-\" to suppress errors is deprecated.")
1.7 rillig 724:
1.56 rillig 725: G.Pkg = NewPackage(t.File("category/pkgbase"))
726: G.Pkg.Plist.Dirs["share/pkgbase"] = &PlistLine{
727: t.NewLine("PLIST", 123, "share/pkgbase/file"),
728: nil,
729: "share/pkgbase/file"}
1.7 rillig 730:
1.56 rillig 731: // A directory that is found in the PLIST.
732: // TODO: Add a test for using this command inside a conditional;
733: // the note should not appear then.
734: test("${RUN} ${INSTALL_DATA_DIR} share/pkgbase ${PREFIX}/share/pkgbase",
735: "NOTE: filename.mk:1: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= share/pkgbase\" "+
736: "instead of \"${INSTALL_DATA_DIR}\".",
737: "WARN: filename.mk:1: The INSTALL_*_DIR commands can only handle one directory at a time.")
1.20 rillig 738:
1.56 rillig 739: // A directory that is not found in the PLIST.
740: test("${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/share/other",
741: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= share/other\" instead of \"${INSTALL_DATA_DIR}\".")
1.6 rillig 742:
1.56 rillig 743: G.Pkg = nil
1.6 rillig 744:
1.56 rillig 745: // See PR 46570, item "1. It does not"
746: // No warning about missing error checking ("set -e").
747: test("for x in 1 2 3; do echo \"$$x\" || exit 1; done",
748: nil...)
1.6 rillig 749: }
750:
1.56 rillig 751: // TODO: Document in detail that strip is not a regular tool.
752: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__strip(c *check.C) {
1.31 rillig 753: t := s.Init(c)
754:
1.56 rillig 755: test := func(shellCommand string) {
756: mklines := t.NewMkLines("filename.mk",
757: "\t"+shellCommand)
1.31 rillig 758:
1.56 rillig 759: mklines.ForEach(func(mkline *MkLine) {
760: ck := NewShellLineChecker(mklines, mkline)
761: ck.CheckShellCommandLine(mkline.ShellCommand())
762: })
763: }
1.31 rillig 764:
1.56 rillig 765: test("${STRIP} executable")
1.31 rillig 766:
767: t.CheckOutputLines(
1.56 rillig 768: "WARN: filename.mk:1: Unknown shell command \"${STRIP}\".",
769: "WARN: filename.mk:1: STRIP is used but not defined.")
1.31 rillig 770:
1.56 rillig 771: t.SetUpVartypes()
1.31 rillig 772:
1.56 rillig 773: test("${STRIP} executable")
1.31 rillig 774:
1.37 rillig 775: t.CheckOutputEmpty()
1.31 rillig 776: }
777:
1.56 rillig 778: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__nofix(c *check.C) {
1.30 rillig 779: t := s.Init(c)
780:
1.56 rillig 781: t.SetUpVartypes()
782: t.SetUpTool("echo", "", AtRunTime)
783: mklines := t.NewMkLines("Makefile",
784: "\techo ${PKGNAME:Q}")
785: ck := NewShellLineChecker(mklines, mklines.mklines[0])
1.30 rillig 786:
1.56 rillig 787: ck.CheckShellCommandLine("echo ${PKGNAME:Q}")
1.30 rillig 788:
789: t.CheckOutputLines(
1.56 rillig 790: "NOTE: Makefile:1: The :Q modifier isn't necessary for ${PKGNAME} here.")
1.33 rillig 791: }
792:
1.56 rillig 793: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__show_autofix(c *check.C) {
1.33 rillig 794: t := s.Init(c)
795:
1.56 rillig 796: t.SetUpCommandLine("-Wall", "--show-autofix")
1.37 rillig 797: t.SetUpVartypes()
1.56 rillig 798: t.SetUpTool("echo", "", AtRunTime)
799: mklines := t.NewMkLines("Makefile",
800: "\techo ${PKGNAME:Q}")
801: ck := NewShellLineChecker(mklines, mklines.mklines[0])
1.33 rillig 802:
1.56 rillig 803: ck.CheckShellCommandLine("echo ${PKGNAME:Q}")
1.33 rillig 804:
805: t.CheckOutputLines(
1.56 rillig 806: "NOTE: Makefile:1: The :Q modifier isn't necessary for ${PKGNAME} here.",
807: "AUTOFIX: Makefile:1: Replacing \"${PKGNAME:Q}\" with \"${PKGNAME}\".")
1.30 rillig 808: }
809:
1.56 rillig 810: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__autofix(c *check.C) {
1.49 rillig 811: t := s.Init(c)
812:
1.56 rillig 813: t.SetUpCommandLine("-Wall", "--autofix")
1.49 rillig 814: t.SetUpVartypes()
1.56 rillig 815: t.SetUpTool("echo", "", AtRunTime)
816: mklines := t.NewMkLines("Makefile",
817: "\techo ${PKGNAME:Q}")
818: ck := NewShellLineChecker(mklines, mklines.mklines[0])
1.49 rillig 819:
1.56 rillig 820: ck.CheckShellCommandLine("echo ${PKGNAME:Q}")
1.49 rillig 821:
1.56 rillig 822: t.CheckOutputLines(
823: "AUTOFIX: Makefile:1: Replacing \"${PKGNAME:Q}\" with \"${PKGNAME}\".")
1.49 rillig 824:
1.56 rillig 825: // TODO: There should be a general way of testing a code in the three modes:
826: // default, --show-autofix, --autofix.
1.49 rillig 827: }
828:
1.56 rillig 829: // TODO: Document the exact purpose of this test, or split it into useful tests.
830: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__implementation(c *check.C) {
1.31 rillig 831: t := s.Init(c)
832:
1.56 rillig 833: t.SetUpVartypes()
1.34 rillig 834: mklines := t.NewMkLines("filename.mk",
1.56 rillig 835: "# dummy")
836: ck := NewShellLineChecker(mklines, mklines.mklines[0])
1.31 rillig 837:
1.56 rillig 838: // foobar="`echo \"foo bar\"`"
839: text := "foobar=\"`echo \\\"foo bar\\\"`\""
1.31 rillig 840:
1.58 ! rillig 841: tokens, rest := splitIntoShellTokens(ck.mkline.Line, text)
1.31 rillig 842:
1.56 rillig 843: t.CheckDeepEquals(tokens, []string{text})
844: t.CheckEquals(rest, "")
1.31 rillig 845:
1.56 rillig 846: mklines.ForEach(func(mkline *MkLine) { ck.CheckWord(text, false, RunTime) })
1.31 rillig 847:
848: t.CheckOutputLines(
1.56 rillig 849: "WARN: filename.mk:1: Unknown shell command \"echo\".")
1.31 rillig 850:
1.56 rillig 851: mklines.ForEach(func(mkline *MkLine) { ck.CheckShellCommandLine(text) })
1.34 rillig 852:
1.56 rillig 853: // No parse errors
854: t.CheckOutputLines(
855: "WARN: filename.mk:1: Unknown shell command \"echo\".")
1.34 rillig 856: }
857:
1.56 rillig 858: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__dollar_without_variable(c *check.C) {
1.31 rillig 859: t := s.Init(c)
860:
1.37 rillig 861: t.SetUpVartypes()
1.56 rillig 862: t.SetUpTool("pax", "", AtRunTime)
1.34 rillig 863: mklines := t.NewMkLines("filename.mk",
1.56 rillig 864: "# dummy")
865: ck := NewShellLineChecker(mklines, mklines.mklines[0])
1.31 rillig 866:
1.56 rillig 867: ck.CheckShellCommandLine("pax -rwpp -s /.*~$$//g . ${DESTDIR}${PREFIX}")
1.31 rillig 868:
869: t.CheckOutputLines(
1.56 rillig 870: "WARN: filename.mk:1: Substitution commands like \"/.*~$$//g\" should always be quoted.")
1.31 rillig 871: }
872:
1.42 rillig 873: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__echo(c *check.C) {
1.20 rillig 874: t := s.Init(c)
875:
1.37 rillig 876: echo := t.SetUpTool("echo", "ECHO", AtRunTime)
1.30 rillig 877: echo.MustUseVarForm = true
1.43 rillig 878: mklines := t.NewMkLines("filename.mk",
1.7 rillig 879: "# dummy")
1.41 rillig 880: mkline := t.NewMkLine("filename.mk", 3, "# dummy")
1.7 rillig 881:
1.43 rillig 882: MkLineChecker{mklines, mkline}.checkText("echo \"hello, world\"")
1.1 rillig 883:
1.20 rillig 884: t.CheckOutputEmpty()
1.7 rillig 885:
1.43 rillig 886: NewShellLineChecker(mklines, mkline).CheckShellCommandLine("echo \"hello, world\"")
1.1 rillig 887:
1.20 rillig 888: t.CheckOutputLines(
1.41 rillig 889: "WARN: filename.mk:3: Please use \"${ECHO}\" instead of \"echo\".")
1.1 rillig 890: }
1.3 rillig 891:
1.42 rillig 892: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__shell_variables(c *check.C) {
1.20 rillig 893: t := s.Init(c)
894:
1.37 rillig 895: t.SetUpVartypes()
896: t.SetUpTool("install", "INSTALL", AtRunTime)
897: t.SetUpTool("cp", "CP", AtRunTime)
898: t.SetUpTool("mv", "MV", AtRunTime)
899: t.SetUpTool("sed", "SED", AtRunTime)
1.44 rillig 900: text := "for f in *.pl; do ${SED} s,@PREFIX@,${PREFIX}, < $f > $f.tmp && ${MV} $f.tmp $f; done"
1.48 rillig 901: mklines := t.NewMkLines("Makefile",
1.49 rillig 902: MkCvsID,
1.48 rillig 903: "",
904: "\t"+text)
1.3 rillig 905:
1.48 rillig 906: ck := NewShellLineChecker(mklines, mklines.mklines[2])
1.42 rillig 907: ck.CheckShellCommandLine(text)
1.3 rillig 908:
1.20 rillig 909: t.CheckOutputLines(
1.58 ! rillig 910: // TODO: Avoid these duplicate diagnostics.
1.35 rillig 911: "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
912: "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
913: "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
914: "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
1.48 rillig 915: "NOTE: Makefile:3: Please use the SUBST framework instead of ${SED} and ${MV}.",
1.58 ! rillig 916: "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
! 917: "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
! 918: "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
! 919: "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
1.57 rillig 920: "WARN: Makefile:3: f is used but not defined.",
921: "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
922: "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.")
1.3 rillig 923:
1.42 rillig 924: ck.CheckShellCommandLine("install -c manpage.1 ${PREFIX}/man/man1/manpage.1")
1.3 rillig 925:
1.20 rillig 926: t.CheckOutputLines(
1.16 rillig 927: "WARN: Makefile:3: Please use ${PKGMANDIR} instead of \"man\".")
1.3 rillig 928:
1.42 rillig 929: ck.CheckShellCommandLine("cp init-script ${PREFIX}/etc/rc.d/service")
1.3 rillig 930:
1.20 rillig 931: t.CheckOutputLines(
1.16 rillig 932: "WARN: Makefile:3: Please use the RCD_SCRIPTS mechanism to install rc.d scripts automatically to ${RCD_SCRIPTS_EXAMPLEDIR}.")
1.3 rillig 933: }
934:
1.42 rillig 935: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__sed_and_mv(c *check.C) {
1.20 rillig 936: t := s.Init(c)
937:
1.37 rillig 938: t.SetUpVartypes()
939: t.SetUpTool("sed", "SED", AtRunTime)
940: t.SetUpTool("mv", "MV", AtRunTime)
1.48 rillig 941: ck := t.NewShellLineChecker("\t${RUN} ${SED} 's,#,// comment:,g' filename > filename.tmp; ${MV} filename.tmp filename")
1.7 rillig 942:
1.42 rillig 943: ck.CheckShellCommandLine(ck.mkline.ShellCommand())
1.7 rillig 944:
1.20 rillig 945: t.CheckOutputLines(
1.48 rillig 946: "NOTE: filename.mk:1: Please use the SUBST framework instead of ${SED} and ${MV}.")
1.7 rillig 947: }
948:
1.42 rillig 949: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__subshell(c *check.C) {
1.20 rillig 950: t := s.Init(c)
951:
1.48 rillig 952: ck := t.NewShellLineChecker("\t${RUN} uname=$$(uname)")
1.7 rillig 953:
1.42 rillig 954: ck.CheckShellCommandLine(ck.mkline.ShellCommand())
1.7 rillig 955:
1.20 rillig 956: t.CheckOutputLines(
1.48 rillig 957: "WARN: filename.mk:1: Invoking subshells via $(...) is not portable enough.")
1.7 rillig 958: }
959:
1.42 rillig 960: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__install_dir(c *check.C) {
1.20 rillig 961: t := s.Init(c)
962:
1.37 rillig 963: t.SetUpVartypes()
1.48 rillig 964: ck := t.NewShellLineChecker("\t${RUN} ${INSTALL_DATA_DIR} ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2")
1.7 rillig 965:
1.42 rillig 966: ck.CheckShellCommandLine(ck.mkline.ShellCommand())
1.7 rillig 967:
1.20 rillig 968: t.CheckOutputLines(
1.48 rillig 969: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= dir1\" instead of \"${INSTALL_DATA_DIR}\".",
970: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= dir2\" instead of \"${INSTALL_DATA_DIR}\".",
971: "WARN: filename.mk:1: The INSTALL_*_DIR commands can only handle one directory at a time.")
1.10 rillig 972:
1.42 rillig 973: ck.CheckShellCommandLine("${INSTALL_DATA_DIR} -d -m 0755 ${DESTDIR}${PREFIX}/share/examples/gdchart")
1.10 rillig 974:
975: // No warning about multiple directories, since 0755 is an option, not an argument.
1.20 rillig 976: t.CheckOutputLines(
1.48 rillig 977: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= share/examples/gdchart\" instead of \"${INSTALL_DATA_DIR}\".")
1.10 rillig 978:
1.56 rillig 979: ck.CheckShellCommandLine("${INSTALL_DATA_DIR} -d -m 0755 ${DESTDIR}${PREFIX}/dir1 ${PREFIX}/dir2")
980:
981: t.CheckOutputLines(
982: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= dir1\" instead of \"${INSTALL_DATA_DIR}\".",
983: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= dir2\" instead of \"${INSTALL_DATA_DIR}\".",
984: "WARN: filename.mk:1: The INSTALL_*_DIR commands can only handle one directory at a time.")
985: }
986:
987: func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__install_option_d(c *check.C) {
988: t := s.Init(c)
989:
990: t.SetUpVartypes()
991: ck := t.NewShellLineChecker("\t${RUN} ${INSTALL} -d ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2")
992:
993: ck.CheckShellCommandLine(ck.mkline.ShellCommand())
994:
995: t.CheckOutputLines(
996: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= dir1\" instead of \"${INSTALL} -d\".",
997: "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= dir2\" instead of \"${INSTALL} -d\".")
998: }
999:
1000: func (s *Suite) Test_ShellLineChecker_checkHiddenAndSuppress(c *check.C) {
1001: t := s.Init(c)
1002:
1003: t.SetUpTool("echo", "ECHO", AtRunTime)
1004: t.SetUpTool("ls", "LS", AtRunTime)
1005: mklines := t.NewMkLines("Makefile",
1006: MkCvsID,
1007: "",
1008: "show-all-targets: .PHONY",
1009: "\t@echo 'hello'",
1010: "\t@ls -l",
1011: "",
1012: "anything-message: .PHONY",
1013: "\t@echo 'may be hidden'",
1014: "\t@ls 'may be hidden'",
1015: "",
1016: "pre-configure:",
1017: "\t@")
1018:
1019: mklines.Check()
1.10 rillig 1020:
1.56 rillig 1021: // No warning about the hidden ls since the target names start
1022: // with "show-" or end with "-message".
1023: t.CheckOutputEmpty()
1.7 rillig 1024: }
1025:
1.56 rillig 1026: func (s *Suite) Test_ShellLineChecker_checkHiddenAndSuppress__no_tracing(c *check.C) {
1.20 rillig 1027: t := s.Init(c)
1028:
1.56 rillig 1029: t.SetUpTool("ls", "LS", AtRunTime)
1030: mklines := t.NewMkLines("Makefile",
1031: MkCvsID,
1032: "",
1033: "pre-configure:",
1034: "\t@ls -l")
1035: t.DisableTracing()
1.7 rillig 1036:
1.56 rillig 1037: mklines.Check()
1.7 rillig 1038:
1.20 rillig 1039: t.CheckOutputLines(
1.56 rillig 1040: "WARN: Makefile:4: The shell command \"ls\" should not be hidden.")
1.7 rillig 1041: }
1042:
1.56 rillig 1043: func (s *Suite) Test_ShellLineChecker_CheckShellCommand__cd_inside_if(c *check.C) {
1.20 rillig 1044: t := s.Init(c)
1045:
1.56 rillig 1046: t.SetUpVartypes()
1047: t.SetUpTool("echo", "ECHO", AtRunTime)
1048: mklines := t.NewMkLines("Makefile",
1.49 rillig 1049: MkCvsID,
1.56 rillig 1050: "",
1051: "\t${RUN} if cd /bin; then echo \"/bin exists.\"; fi")
1.7 rillig 1052:
1.27 rillig 1053: mklines.Check()
1.7 rillig 1054:
1.56 rillig 1055: t.CheckOutputLines(
1056: "ERROR: Makefile:3: The Solaris /bin/sh cannot handle \"cd\" inside conditionals.")
1.7 rillig 1057: }
1.9 rillig 1058:
1.56 rillig 1059: func (s *Suite) Test_ShellLineChecker_CheckShellCommand__negated_pipe(c *check.C) {
1.34 rillig 1060: t := s.Init(c)
1061:
1.37 rillig 1062: t.SetUpVartypes()
1.56 rillig 1063: t.SetUpTool("echo", "ECHO", AtRunTime)
1064: t.SetUpTool("test", "TEST", AtRunTime)
1065: mklines := t.NewMkLines("Makefile",
1066: MkCvsID,
1067: "",
1068: "\t${RUN} if ! test -f /etc/passwd; then echo \"passwd is missing.\"; fi")
1.34 rillig 1069:
1.56 rillig 1070: mklines.Check()
1.34 rillig 1071:
1.56 rillig 1072: t.CheckOutputLines(
1073: "WARN: Makefile:3: The Solaris /bin/sh does not support negation of shell commands.")
1.49 rillig 1074: }
1075:
1.56 rillig 1076: func (s *Suite) Test_ShellLineChecker_CheckShellCommand__subshell(c *check.C) {
1.49 rillig 1077: t := s.Init(c)
1078:
1.56 rillig 1079: t.SetUpTool("echo", "ECHO", AtRunTime)
1080: t.SetUpTool("expr", "EXPR", AtRunTime)
1081: mklines := t.NewMkLines("Makefile",
1.49 rillig 1082: MkCvsID,
1083: "",
1084: "pre-configure:",
1.56 rillig 1085: "\t@(echo ok)",
1086: "\techo $$(uname -r); echo $$(expr 4 '*' $$(echo 1024))",
1087: "\t@(echo nb$$(uname -r) $$(${EXPR} 4 \\* $$(echo 1024)))")
1.49 rillig 1088:
1089: mklines.Check()
1090:
1.56 rillig 1091: // FIXME: Fix the parse errors (nested subshells).
1092: // FIXME: Fix the duplicate diagnostic in line 6.
1093: // FIXME: "(" is not a shell command, it's an operator.
1.49 rillig 1094: t.CheckOutputLines(
1.56 rillig 1095: "WARN: Makefile:4: The shell command \"(\" should not be hidden.",
1096: "WARN: Makefile:5: Internal pkglint error in ShTokenizer.ShAtom at \"$$(echo 1024))\" (quoting=S).",
1097: "WARN: Makefile:5: Invoking subshells via $(...) is not portable enough.",
1098: "WARN: Makefile:6: Internal pkglint error in ShTokenizer.ShAtom at \"$$(echo 1024)))\" (quoting=S).",
1099: "WARN: Makefile:6: The shell command \"(\" should not be hidden.",
1100: "WARN: Makefile:6: Internal pkglint error in ShTokenizer.ShAtom at \"$$(echo 1024)))\" (quoting=S).",
1101: "WARN: Makefile:6: Invoking subshells via $(...) is not portable enough.")
1.49 rillig 1102: }
1103:
1.56 rillig 1104: func (s *Suite) Test_ShellLineChecker_CheckShellCommand__case_patterns_from_variable(c *check.C) {
1.49 rillig 1105: t := s.Init(c)
1106:
1107: t.SetUpVartypes()
1.56 rillig 1108: mklines := t.NewMkLines("Makefile",
1.49 rillig 1109: MkCvsID,
1110: "",
1111: "pre-configure:",
1.56 rillig 1112: "\tcase $$file in ${CHECK_PERMS_SKIP:@pattern@${pattern}) ;;@} *) continue; esac")
1.49 rillig 1113:
1114: mklines.Check()
1115:
1.56 rillig 1116: // TODO: Ensure that the shell word is really only one variable use.
1117: // TODO: Ensure that the last modifier is :@@@.
1118: // TODO: Ensure that the replacement is a well-formed case-item.
1119: // TODO: Ensure that the replacement contains ";;" as the last shell token.
1120: t.CheckOutputEmpty()
1.34 rillig 1121: }
1122:
1.56 rillig 1123: func (s *Suite) Test_ShellLineChecker_CheckWord(c *check.C) {
1.20 rillig 1124: t := s.Init(c)
1125:
1.56 rillig 1126: t.SetUpVartypes()
1127:
1128: test := func(shellWord string, checkQuoting bool, diagnostics ...string) {
1129: // See checkVaruseUndefined and checkVarassignLeftNotUsed.
1130: ck := t.NewShellLineChecker("\t echo " + shellWord)
1131: ck.CheckWord(shellWord, checkQuoting, RunTime)
1132: t.CheckOutput(diagnostics)
1133: }
1134:
1135: // No warning for the outer variable since it is completely indirect.
1136: // The inner variable ${list} must still be defined, though.
1137: test("${${list}}", false,
1138: "WARN: filename.mk:1: list is used but not defined.")
1139:
1140: // No warning for variables that are partly indirect.
1141: // TODO: Why not?
1142: test("${SED_FILE.${id}}", false,
1143: "WARN: filename.mk:1: id is used but not defined.")
1144:
1145: // TODO: Since $@ refers to ${.TARGET} and not sh.argv, there is no point in checking for quotes.
1146: // TODO: Having the same tests for $$@ would be much more interesting.
1147:
1148: // The unquoted $@ takes a different code path in pkglint than the quoted $@.
1149: test("$@", false,
1150: "WARN: filename.mk:1: Please use \"${.TARGET}\" instead of \"$@\".")
1.34 rillig 1151:
1.56 rillig 1152: // When $@ appears as part of a shell token, it takes another code path in pkglint.
1153: test("-$@-", false,
1154: "WARN: filename.mk:1: Please use \"${.TARGET}\" instead of \"$@\".")
1.9 rillig 1155:
1.56 rillig 1156: // The unquoted $@ takes a different code path in pkglint than the quoted $@.
1157: test("\"$@\"", false,
1158: "WARN: filename.mk:1: Please use \"${.TARGET}\" instead of \"$@\".")
1.34 rillig 1159:
1.56 rillig 1160: test("${COMMENT:Q}", true,
1161: nil...)
1.34 rillig 1162:
1.56 rillig 1163: test("\"${DISTINFO_FILE:Q}\"", true,
1164: "NOTE: filename.mk:1: The :Q modifier isn't necessary for ${DISTINFO_FILE} here.")
1.19 rillig 1165:
1.56 rillig 1166: test("embed${DISTINFO_FILE:Q}ded", true,
1167: "NOTE: filename.mk:1: The :Q modifier isn't necessary for ${DISTINFO_FILE} here.")
1.34 rillig 1168:
1.56 rillig 1169: test("s,\\.,,", true,
1170: nil...)
1.34 rillig 1171:
1.56 rillig 1172: test("\"s,\\.,,\"", true,
1173: nil...)
1174: }
1.30 rillig 1175:
1.56 rillig 1176: func (s *Suite) Test_ShellLineChecker_CheckWord__dollar_without_variable(c *check.C) {
1177: t := s.Init(c)
1.34 rillig 1178:
1.56 rillig 1179: ck := t.NewShellLineChecker("# dummy")
1.34 rillig 1180:
1.56 rillig 1181: ck.CheckWord("/.*~$$//g", false, RunTime) // Typical argument to pax(1).
1.34 rillig 1182:
1.56 rillig 1183: t.CheckOutputEmpty()
1.30 rillig 1184: }
1185:
1.56 rillig 1186: func (s *Suite) Test_ShellLineChecker_CheckWord__backslash_plus(c *check.C) {
1.30 rillig 1187: t := s.Init(c)
1188:
1.56 rillig 1189: t.SetUpTool("find", "FIND", AtRunTime)
1190: ck := t.NewShellLineChecker("\tfind . -exec rm -rf {} \\+")
1.30 rillig 1191:
1.56 rillig 1192: ck.CheckShellCommandLine(ck.mkline.ShellCommand())
1.30 rillig 1193:
1.56 rillig 1194: // A backslash before any other character than " \ ` is discarded by the parser.
1195: t.CheckOutputEmpty()
1.9 rillig 1196: }
1.20 rillig 1197:
1.56 rillig 1198: func (s *Suite) Test_ShellLineChecker_CheckWord__squot_dollar(c *check.C) {
1.20 rillig 1199: t := s.Init(c)
1200:
1.56 rillig 1201: ck := t.NewShellLineChecker("\t'$")
1.20 rillig 1202:
1.56 rillig 1203: ck.CheckWord(ck.mkline.ShellCommand(), false, RunTime)
1.20 rillig 1204:
1.56 rillig 1205: // FIXME: Should be parsed correctly. Make passes the dollar through (probably),
1206: // and the shell parser should complain about the unfinished string literal.
1.20 rillig 1207: t.CheckOutputLines(
1.56 rillig 1208: "WARN: filename.mk:1: Internal pkglint error in ShTokenizer.ShAtom at \"$\" (quoting=s).",
1209: "WARN: filename.mk:1: Internal pkglint error in ShellLine.CheckWord at \"'$\" (quoting=s), rest: $")
1.20 rillig 1210: }
1.28 rillig 1211:
1.56 rillig 1212: func (s *Suite) Test_ShellLineChecker_CheckWord__dquot_dollar(c *check.C) {
1.28 rillig 1213: t := s.Init(c)
1214:
1.56 rillig 1215: ck := t.NewShellLineChecker("\t\"$")
1.28 rillig 1216:
1.56 rillig 1217: ck.CheckWord(ck.mkline.ShellCommand(), false, RunTime)
1.28 rillig 1218:
1.56 rillig 1219: // FIXME: Make consumes the dollar silently.
1220: // This could be worth another pkglint warning.
1221: t.CheckOutputEmpty()
1.28 rillig 1222: }
1223:
1.56 rillig 1224: func (s *Suite) Test_ShellLineChecker_CheckWord__dollar_subshell(c *check.C) {
1.28 rillig 1225: t := s.Init(c)
1226:
1.56 rillig 1227: ck := t.NewShellLineChecker("\t$$(echo output)")
1.28 rillig 1228:
1.56 rillig 1229: ck.CheckWord(ck.mkline.ShellCommand(), false, RunTime)
1.28 rillig 1230:
1231: t.CheckOutputLines(
1.56 rillig 1232: "WARN: filename.mk:1: Invoking subshells via $(...) is not portable enough.")
1.28 rillig 1233: }
1.30 rillig 1234:
1.56 rillig 1235: func (s *Suite) Test_ShellLineChecker_CheckWord__PKGMANDIR(c *check.C) {
1.31 rillig 1236: t := s.Init(c)
1237:
1.56 rillig 1238: t.SetUpVartypes()
1239: mklines := t.NewMkLines("chat/ircII/Makefile",
1.49 rillig 1240: MkCvsID,
1.56 rillig 1241: "CONFIGURE_ARGS+=--mandir=${DESTDIR}${PREFIX}/man",
1242: "CONFIGURE_ARGS+=--mandir=${DESTDIR}${PREFIX}/${PKGMANDIR}")
1.31 rillig 1243:
1244: mklines.Check()
1245:
1246: t.CheckOutputLines(
1.56 rillig 1247: "WARN: chat/ircII/Makefile:2: Please use ${PKGMANDIR} instead of \"man\".",
1248: "NOTE: chat/ircII/Makefile:2: This variable value should be aligned to column 25.",
1249: "NOTE: chat/ircII/Makefile:3: This variable value should be aligned to column 25.")
1.31 rillig 1250: }
1251:
1.56 rillig 1252: func (s *Suite) Test_ShellLineChecker_CheckWord__empty(c *check.C) {
1.31 rillig 1253: t := s.Init(c)
1254:
1.37 rillig 1255: t.SetUpVartypes()
1.56 rillig 1256:
1.31 rillig 1257: mklines := t.NewMkLines("Makefile",
1.49 rillig 1258: MkCvsID,
1.31 rillig 1259: "",
1.56 rillig 1260: "JAVA_CLASSPATH=\t# empty")
1.31 rillig 1261:
1262: mklines.Check()
1263:
1.51 rillig 1264: t.CheckOutputEmpty()
1.31 rillig 1265: }
1266:
1.56 rillig 1267: func (s *Suite) Test_ShellLineChecker_checkWordQuoting(c *check.C) {
1.31 rillig 1268: t := s.Init(c)
1269:
1.56 rillig 1270: t.SetUpVartypes()
1271: t.SetUpTool("grep", "GREP", AtRunTime)
1272:
1273: test := func(input string, diagnostics ...string) {
1274: mklines := t.NewMkLines("module.mk",
1275: "\t"+input)
1276: ck := NewShellLineChecker(mklines, mklines.mklines[0])
1277:
1278: ck.checkWordQuoting(ck.mkline.ShellCommand(), true, RunTime)
1.31 rillig 1279:
1.56 rillig 1280: t.CheckOutput(diagnostics)
1281: }
1.31 rillig 1282:
1.56 rillig 1283: test(
1284: "socklen=`${GREP} 'expr' ${WRKSRC}/config.h`",
1285: nil...)
1.31 rillig 1286:
1.56 rillig 1287: test(
1288: "s,$$from,$$to,",
1289: "WARN: module.mk:1: Unquoted shell variable \"from\".",
1290: "WARN: module.mk:1: Unquoted shell variable \"to\".")
1.48 rillig 1291:
1.56 rillig 1292: // This variable is typically defined by GNU Configure,
1293: // which cannot handle directories with special characters.
1294: // Therefore using it unquoted is considered safe.
1295: test(
1296: "${PREFIX}/$$bindir/program",
1297: nil...)
1.48 rillig 1298:
1.56 rillig 1299: test(
1300: "$$@",
1301: "WARN: module.mk:1: The $@ shell variable should only be used in double quotes.")
1.48 rillig 1302:
1.56 rillig 1303: // TODO: Add separate tests for "set +e" and "set -e".
1304: test(
1305: "$$?",
1306: "WARN: module.mk:1: The $? shell variable is often not available in \"set -e\" mode.")
1.48 rillig 1307:
1.56 rillig 1308: test(
1309: "$$(cat /bin/true)",
1310: "WARN: module.mk:1: Invoking subshells via $(...) is not portable enough.")
1.30 rillig 1311:
1.56 rillig 1312: test(
1313: "\"$$\"",
1314: nil...)
1.30 rillig 1315:
1.56 rillig 1316: test(
1317: "$$$$",
1318: nil...)
1.30 rillig 1319:
1.56 rillig 1320: test(
1321: "``",
1322: nil...)
1.30 rillig 1323: }
1324:
1.56 rillig 1325: func (s *Suite) Test_ShellLineChecker_unescapeBackticks__unfinished(c *check.C) {
1.31 rillig 1326: t := s.Init(c)
1327:
1.56 rillig 1328: mklines := t.NewMkLines("filename.mk",
1.49 rillig 1329: MkCvsID,
1.31 rillig 1330: "",
1.49 rillig 1331: "pre-configure:",
1.56 rillig 1332: "\t`${VAR}", // Error in first shell word
1333: "\techo `${VAR}") // Error after first shell word
1.31 rillig 1334:
1.56 rillig 1335: // Breakpoint in ShellLine.CheckShellCommand
1336: // Breakpoint in ShellLine.CheckToken
1337: // Breakpoint in ShellLine.unescapeBackticks
1.31 rillig 1338: mklines.Check()
1339:
1340: t.CheckOutputLines(
1.56 rillig 1341: "WARN: filename.mk:4: Pkglint ShellLine.CheckShellCommand: splitIntoShellTokens couldn't parse \"`${VAR}\"",
1342: "WARN: filename.mk:5: Pkglint ShellLine.CheckShellCommand: splitIntoShellTokens couldn't parse \"`${VAR}\"")
1.31 rillig 1343: }
1344:
1.56 rillig 1345: func (s *Suite) Test_ShellLineChecker_unescapeBackticks__unfinished_direct(c *check.C) {
1.49 rillig 1346: t := s.Init(c)
1347:
1.56 rillig 1348: mklines := t.NewMkLines("dummy.mk",
1.49 rillig 1349: MkCvsID,
1.56 rillig 1350: "\t# shell command")
1.49 rillig 1351:
1.56 rillig 1352: // This call is unrealistic. It doesn't happen in practice, and this
1353: // direct, forcing test is only to reach the code coverage.
1354: atoms := []*ShAtom{
1355: NewShAtom(shtText, "`", shqBackt)}
1356: NewShellLineChecker(mklines, mklines.mklines[1]).
1357: unescapeBackticks(&atoms, shqBackt)
1.49 rillig 1358:
1359: t.CheckOutputLines(
1.56 rillig 1360: "ERROR: dummy.mk:2: Unfinished backticks after \"\".")
1.49 rillig 1361: }
1362:
1.56 rillig 1363: func (s *Suite) Test_ShellLineChecker_unescapeBackticks(c *check.C) {
1.49 rillig 1364: t := s.Init(c)
1365:
1.56 rillig 1366: test := func(input string, expectedOutput string, expectedRest string, diagnostics ...string) {
1367: ck := t.NewShellLineChecker("# dummy")
1.49 rillig 1368:
1.56 rillig 1369: tok := NewShTokenizer(nil, input, false)
1370: atoms := tok.ShAtoms()
1.49 rillig 1371:
1.56 rillig 1372: // Set up the correct quoting mode for the test by skipping
1373: // uninteresting atoms at the beginning.
1374: q := shqPlain
1375: for atoms[0].MkText != "`" {
1376: q = atoms[0].Quoting
1377: atoms = atoms[1:]
1378: }
1379: t.CheckEquals(tok.Rest(), "")
1.49 rillig 1380:
1.56 rillig 1381: backtCommand := ck.unescapeBackticks(&atoms, q)
1.49 rillig 1382:
1.56 rillig 1383: var actualRest strings.Builder
1384: for _, atom := range atoms {
1385: actualRest.WriteString(atom.MkText)
1386: }
1.31 rillig 1387:
1.56 rillig 1388: t.CheckEquals(backtCommand, expectedOutput)
1389: t.CheckEquals(actualRest.String(), expectedRest)
1390: t.CheckOutput(diagnostics)
1391: }
1.31 rillig 1392:
1.56 rillig 1393: test("`echo`end", "echo", "end")
1394: test("`echo $$var`end", "echo $$var", "end")
1395: test("``end", "", "end")
1396: test("`echo \"hello\"`end", "echo \"hello\"", "end")
1397: test("`echo 'hello'`end", "echo 'hello'", "end")
1398: test("`echo '\\\\\\\\'`end", "echo '\\\\'", "end")
1.31 rillig 1399:
1.56 rillig 1400: // Only the characters " $ ` \ are unescaped. All others stay the same.
1401: test("`echo '\\n'`end", "echo '\\n'", "end",
1402: // TODO: Add more details regarding which backslash is meant.
1403: "WARN: filename.mk:1: Backslashes should be doubled inside backticks.")
1404: test("\tsocklen=`${GREP} 'expr' ${WRKSRC}/config.h`", "${GREP} 'expr' ${WRKSRC}/config.h", "")
1.31 rillig 1405:
1.56 rillig 1406: // The 2xx test cases are in shqDquot mode.
1.39 rillig 1407:
1.56 rillig 1408: test("\"`echo`\"", "echo", "\"")
1409: test("\"`echo \"\"`\"", "echo \"\"", "\"",
1410: "WARN: filename.mk:1: Double quotes inside backticks inside double quotes are error prone.")
1.39 rillig 1411:
1.56 rillig 1412: // varname="`echo \"one two\" "\ " "three"`"
1413: test(
1414: "varname=\"`echo \\\"one two\\\" \"\\ \" \"three\"`\"",
1415: "echo \"one two\" \"\\ \" \"three\"",
1416: "\"",
1.39 rillig 1417:
1.56 rillig 1418: // TODO: Add more details regarding which backslash and backtick is meant.
1419: "WARN: filename.mk:1: Backslashes should be doubled inside backticks.",
1420: "WARN: filename.mk:1: Double quotes inside backticks inside double quotes are error prone.",
1421: "WARN: filename.mk:1: Double quotes inside backticks inside double quotes are error prone.")
1.39 rillig 1422: }
1423:
1.56 rillig 1424: func (s *Suite) Test_ShellLineChecker_unescapeBackticks__dquotBacktDquot(c *check.C) {
1.30 rillig 1425: t := s.Init(c)
1426:
1.56 rillig 1427: t.SetUpTool("echo", "", AtRunTime)
1428: mklines := t.NewMkLines("dummy.mk",
1.49 rillig 1429: MkCvsID,
1.56 rillig 1430: "\t var=\"`echo \"\"`\"")
1.30 rillig 1431:
1432: mklines.Check()
1433:
1434: t.CheckOutputLines(
1.56 rillig 1435: "WARN: dummy.mk:2: Double quotes inside backticks inside double quotes are error prone.")
1.30 rillig 1436: }
1437:
1.56 rillig 1438: func (s *Suite) Test_ShellLineChecker_checkShVarUsePlain__default_warning_level(c *check.C) {
1.30 rillig 1439: t := s.Init(c)
1440:
1.56 rillig 1441: t.SetUpCommandLine( /* none */ )
1442: t.SetUpVartypes()
1443: t.SetUpTool("echo", "", AtRunTime)
1444:
1445: mklines := t.NewMkLines("filename.mk",
1.49 rillig 1446: MkCvsID,
1.56 rillig 1447: "CONFIGURE_ARGS+=\techo $$@ $$var",
1.30 rillig 1448: "",
1.56 rillig 1449: "pre-configure:",
1450: "\techo $$@ $$var")
1.30 rillig 1451:
1452: mklines.Check()
1453:
1.56 rillig 1454: // Using $@ outside of double quotes is so obviously wrong that
1455: // the warning is issued by default.
1.30 rillig 1456: t.CheckOutputLines(
1.56 rillig 1457: "WARN: filename.mk:2: The $@ shell variable should only be used in double quotes.",
1458: "WARN: filename.mk:5: The $@ shell variable should only be used in double quotes.")
1.30 rillig 1459: }
1460:
1.56 rillig 1461: func (s *Suite) Test_ShellLineChecker_checkShVarUsePlain__Wall(c *check.C) {
1.30 rillig 1462: t := s.Init(c)
1463:
1.56 rillig 1464: t.SetUpVartypes()
1465: t.SetUpTool("echo", "", AtRunTime)
1466:
1467: mklines := t.NewMkLines("filename.mk",
1.49 rillig 1468: MkCvsID,
1.56 rillig 1469: "CONFIGURE_ARGS+=\techo $$@ $$var",
1470: "",
1.30 rillig 1471: "pre-configure:",
1.56 rillig 1472: "\techo $$@ $$var")
1.31 rillig 1473:
1474: mklines.Check()
1475:
1.56 rillig 1476: // FIXME: It is inconsistent that the check for unquoted shell
1477: // variables is enabled for CONFIGURE_ARGS (where shell variables
1478: // don't make sense at all) but not for real shell commands.
1.31 rillig 1479: t.CheckOutputLines(
1.56 rillig 1480: "WARN: filename.mk:2: The $@ shell variable should only be used in double quotes.",
1481: "WARN: filename.mk:2: Unquoted shell variable \"var\".",
1482: "WARN: filename.mk:5: The $@ shell variable should only be used in double quotes.")
1.31 rillig 1483: }
1484:
1.56 rillig 1485: func (s *Suite) Test_ShellLineChecker_variableNeedsQuoting(c *check.C) {
1.31 rillig 1486: t := s.Init(c)
1487:
1.56 rillig 1488: test := func(shVarname string, expected bool) {
1489: t.CheckEquals((*ShellLineChecker).variableNeedsQuoting(nil, shVarname), expected)
1.46 rillig 1490: }
1491:
1.56 rillig 1492: test("#", false) // A length is always an integer.
1493: test("?", false) // The exit code is always an integer.
1494: test("$", false) // The PID is always an integer.
1.46 rillig 1495:
1.56 rillig 1496: // In most cases, file and directory names don't contain special characters,
1497: // and if they do, the package will probably not build. Therefore pkglint
1498: // doesn't require them to be quoted, but doing so does not hurt.
1499: test("d", false) // Typically used for directories.
1500: test("f", false) // Typically used for files.
1501: test("i", false) // Typically used for literal values without special characters.
1502: test("id", false) // Identifiers usually don't use special characters.
1503: test("dir", false) // See d above.
1504: test("file", false) // See f above.
1505: test("src", false) // Typically used when copying files or directories.
1506: test("dst", false) // Typically used when copying files or directories.
1.46 rillig 1507:
1.56 rillig 1508: test("bindir", false) // A typical GNU-style directory.
1509: test("mandir", false) // A typical GNU-style directory.
1510: test("prefix", false) //
1.46 rillig 1511:
1.56 rillig 1512: test("bindirs", true) // A list of directories is typically separated by spaces.
1513: test("var", true) // Other variables are unknown, so they should be quoted.
1514: test("0", true) // The program name may contain special characters when given as full path.
1515: test("1", true) // Command line arguments can be arbitrary strings.
1516: test("comment", true) // Comments can be arbitrary strings.
1.31 rillig 1517: }
1518:
1.56 rillig 1519: func (s *Suite) Test_ShellLineChecker_variableNeedsQuoting__integration(c *check.C) {
1.49 rillig 1520: t := s.Init(c)
1521:
1522: t.SetUpVartypes()
1.56 rillig 1523: t.SetUpTool("cp", "", AtRunTime)
1524: mklines := t.NewMkLines("filename.mk",
1525: MkCvsID,
1526: "",
1527: // It's a bit silly to use shell variables in CONFIGURE_ARGS,
1528: // but as of January 2019 that's the only way to run ShellLine.variableNeedsQuoting.
1529: "CONFIGURE_ARGS+=\t; cp $$dir $$\\# $$target",
1530: "pre-configure:",
1531: "\tcp $$dir $$\\# $$target")
1.49 rillig 1532:
1.56 rillig 1533: mklines.Check()
1.49 rillig 1534:
1.56 rillig 1535: // As of January 2019, the quoting check is disabled for real shell commands.
1536: // See ShellLine.CheckShellCommand, spc.checkWord.
1537: t.CheckOutputLines(
1538: "WARN: filename.mk:3: Unquoted shell variable \"target\".")
1539: }
1.49 rillig 1540:
1.56 rillig 1541: func (s *Suite) Test_ShellLineChecker_checkInstallCommand(c *check.C) {
1542: t := s.Init(c)
1.49 rillig 1543:
1.56 rillig 1544: mklines := t.NewMkLines("filename.mk",
1545: "\t# dummy")
1546: mklines.target = "do-install"
1.49 rillig 1547:
1.56 rillig 1548: ck := NewShellLineChecker(mklines, mklines.mklines[0])
1.49 rillig 1549:
1.56 rillig 1550: ck.checkInstallCommand("sed")
1.53 rillig 1551:
1.56 rillig 1552: t.CheckOutputLines(
1553: "WARN: filename.mk:1: The shell command \"sed\" should not be used in the install phase.")
1.53 rillig 1554:
1.56 rillig 1555: ck.checkInstallCommand("cp")
1.53 rillig 1556:
1557: t.CheckOutputLines(
1.56 rillig 1558: "WARN: filename.mk:1: ${CP} should not be used to install files.")
1.53 rillig 1559: }
1560:
1.56 rillig 1561: func (s *Suite) Test_splitIntoShellTokens__line_continuation(c *check.C) {
1.50 rillig 1562: t := s.Init(c)
1563:
1.58 ! rillig 1564: line := t.NewLine("filename.mk", 1, "")
! 1565: words, rest := splitIntoShellTokens(line, "if true; then \\")
1.50 rillig 1566:
1.56 rillig 1567: t.CheckDeepEquals(words, []string{"if", "true", ";", "then"})
1568: t.CheckEquals(rest, "\\")
1.50 rillig 1569:
1570: t.CheckOutputLines(
1.58 ! rillig 1571: "WARN: filename.mk:1: Internal pkglint error in ShTokenizer.ShAtom at \"\\\\\" (quoting=plain).")
1.50 rillig 1572: }
1573:
1.56 rillig 1574: func (s *Suite) Test_splitIntoShellTokens__dollar_slash(c *check.C) {
1.31 rillig 1575: t := s.Init(c)
1576:
1.58 ! rillig 1577: line := t.NewLine("filename.mk", 1, "")
! 1578: words, rest := splitIntoShellTokens(line, "pax -s /.*~$$//g")
1.31 rillig 1579:
1.56 rillig 1580: t.CheckDeepEquals(words, []string{"pax", "-s", "/.*~$$//g"})
1581: t.CheckEquals(rest, "")
1.31 rillig 1582: }
1583:
1.56 rillig 1584: func (s *Suite) Test_splitIntoShellTokens__dollar_subshell(c *check.C) {
1.31 rillig 1585: t := s.Init(c)
1586:
1.58 ! rillig 1587: line := t.NewLine("filename.mk", 1, "")
! 1588: words, rest := splitIntoShellTokens(line, "id=$$(${AWK} '{print}' < ${WRKSRC}/idfile) && echo \"$$id\"")
1.31 rillig 1589:
1.56 rillig 1590: t.CheckDeepEquals(words, []string{"id=$$(${AWK} '{print}' < ${WRKSRC}/idfile)", "&&", "echo", "\"$$id\""})
1591: t.CheckEquals(rest, "")
1.31 rillig 1592: }
1593:
1.56 rillig 1594: func (s *Suite) Test_splitIntoShellTokens__semicolons(c *check.C) {
1.48 rillig 1595: t := s.Init(c)
1596:
1.58 ! rillig 1597: line := t.NewLine("filename.mk", 1, "")
! 1598: words, rest := splitIntoShellTokens(line, "word1 word2;;;")
1.48 rillig 1599:
1.56 rillig 1600: t.CheckDeepEquals(words, []string{"word1", "word2", ";;", ";"})
1601: t.CheckEquals(rest, "")
1.48 rillig 1602: }
1603:
1.56 rillig 1604: func (s *Suite) Test_splitIntoShellTokens__whitespace(c *check.C) {
1.31 rillig 1605: t := s.Init(c)
1606:
1.56 rillig 1607: text := "\t${RUN} cd ${WRKSRC}&&(${ECHO} ${PERL5:Q};${ECHO})|${BASH} ./install"
1.58 ! rillig 1608: line := t.NewLine("filename.mk", 1, "")
! 1609: words, rest := splitIntoShellTokens(line, text)
1.56 rillig 1610:
1611: t.CheckDeepEquals(words, []string{
1612: "${RUN}",
1613: "cd", "${WRKSRC}",
1614: "&&", "(", "${ECHO}", "${PERL5:Q}", ";", "${ECHO}", ")",
1615: "|", "${BASH}", "./install"})
1616: t.CheckEquals(rest, "")
1617: }
1.30 rillig 1618:
1.56 rillig 1619: func (s *Suite) Test_splitIntoShellTokens__finished_dquot(c *check.C) {
1620: t := s.Init(c)
1.49 rillig 1621:
1.56 rillig 1622: text := "\"\""
1.58 ! rillig 1623: line := t.NewLine("filename.mk", 1, "")
! 1624: words, rest := splitIntoShellTokens(line, text)
1.49 rillig 1625:
1.56 rillig 1626: t.CheckDeepEquals(words, []string{"\"\""})
1627: t.CheckEquals(rest, "")
1628: }
1.49 rillig 1629:
1.56 rillig 1630: func (s *Suite) Test_splitIntoShellTokens__unfinished_dquot(c *check.C) {
1631: t := s.Init(c)
1.49 rillig 1632:
1.56 rillig 1633: text := "\t\""
1.58 ! rillig 1634: line := t.NewLine("filename.mk", 1, "")
! 1635: words, rest := splitIntoShellTokens(line, text)
1.49 rillig 1636:
1.56 rillig 1637: c.Check(words, check.IsNil)
1638: t.CheckEquals(rest, "\"")
1639: }
1.49 rillig 1640:
1.56 rillig 1641: func (s *Suite) Test_splitIntoShellTokens__unescaped_dollar_in_dquot(c *check.C) {
1642: t := s.Init(c)
1.49 rillig 1643:
1.56 rillig 1644: text := "echo \"$$\""
1.58 ! rillig 1645: line := t.NewLine("filename.mk", 1, "")
! 1646: words, rest := splitIntoShellTokens(line, text)
1.49 rillig 1647:
1.56 rillig 1648: t.CheckDeepEquals(words, []string{"echo", "\"$$\""})
1649: t.CheckEquals(rest, "")
1.49 rillig 1650:
1.56 rillig 1651: t.CheckOutputEmpty()
1652: }
1.49 rillig 1653:
1.56 rillig 1654: func (s *Suite) Test_splitIntoShellTokens__varuse_with_embedded_space_and_other_vars(c *check.C) {
1655: t := s.Init(c)
1.49 rillig 1656:
1.56 rillig 1657: varuseWord := "${GCONF_SCHEMAS:@.s.@${INSTALL_DATA} ${WRKSRC}/src/common/dbus/${.s.} ${DESTDIR}${GCONF_SCHEMAS_DIR}/@}"
1.58 ! rillig 1658: line := t.NewLine("filename.mk", 1, "")
! 1659: words, rest := splitIntoShellTokens(line, varuseWord)
1.49 rillig 1660:
1.56 rillig 1661: t.CheckDeepEquals(words, []string{varuseWord})
1662: t.CheckEquals(rest, "")
1663: }
1.49 rillig 1664:
1.56 rillig 1665: // Two shell variables, next to each other,
1666: // are two separate atoms but count as a single token.
1667: func (s *Suite) Test_splitIntoShellTokens__two_shell_variables(c *check.C) {
1668: t := s.Init(c)
1.49 rillig 1669:
1.56 rillig 1670: code := "echo $$i$$j"
1.58 ! rillig 1671: line := t.NewLine("filename.mk", 1, "")
! 1672: words, rest := splitIntoShellTokens(line, code)
1.49 rillig 1673:
1.56 rillig 1674: t.CheckDeepEquals(words, []string{"echo", "$$i$$j"})
1675: t.CheckEquals(rest, "")
1676: }
1.49 rillig 1677:
1.56 rillig 1678: func (s *Suite) Test_splitIntoShellTokens__varuse_with_embedded_space(c *check.C) {
1679: t := s.Init(c)
1.49 rillig 1680:
1.58 ! rillig 1681: line := t.NewLine("filename.mk", 1, "")
! 1682: words, rest := splitIntoShellTokens(line, "${VAR:S/ /_/g}")
1.30 rillig 1683:
1.56 rillig 1684: t.CheckDeepEquals(words, []string{"${VAR:S/ /_/g}"})
1685: t.CheckEquals(rest, "")
1686: }
1.49 rillig 1687:
1.56 rillig 1688: func (s *Suite) Test_splitIntoShellTokens__redirect(c *check.C) {
1689: t := s.Init(c)
1.49 rillig 1690:
1.58 ! rillig 1691: line := t.NewLine("filename.mk", 1, "")
! 1692: words, rest := splitIntoShellTokens(line, "echo 1>output 2>>append 3>|clobber 4>&5 6<input >>append")
1.49 rillig 1693:
1.56 rillig 1694: t.CheckDeepEquals(words, []string{
1695: "echo",
1696: "1>", "output",
1697: "2>>", "append",
1698: "3>|", "clobber",
1699: "4>&", "5",
1700: "6<", "input",
1701: ">>", "append"})
1702: t.CheckEquals(rest, "")
1.49 rillig 1703:
1.58 ! rillig 1704: words, rest = splitIntoShellTokens(line, "echo 1> output 2>> append 3>| clobber 4>& 5 6< input >> append")
1.49 rillig 1705:
1.56 rillig 1706: t.CheckDeepEquals(words, []string{
1707: "echo",
1708: "1>", "output",
1709: "2>>", "append",
1710: "3>|", "clobber",
1711: "4>&", "5",
1712: "6<", "input",
1713: ">>", "append"})
1714: t.CheckEquals(rest, "")
1.30 rillig 1715: }
CVSweb <webmaster@jp.NetBSD.org>