Annotation of pkgsrc/pkgtools/pkglint/files/shell_test.go, Revision 1.7
1.1 rillig 1: package main
2:
3: import (
4: check "gopkg.in/check.v1"
5: )
6:
1.7 ! rillig 7: func (s *Suite) TestReShellToken(c *check.C) {
! 8: re := `^(?:` + reShellToken + `)$`
! 9: matches := check.NotNil
! 10: doesntMatch := check.IsNil
! 11:
! 12: c.Check(match("", re), doesntMatch)
! 13: c.Check(match("$var", re), matches)
! 14: c.Check(match("$var$var", re), matches)
! 15: c.Check(match("$var;;", re), doesntMatch) // More than one token
! 16: c.Check(match("'single-quoted'", re), matches)
! 17: c.Check(match("\"", re), doesntMatch) // Incomplete string
! 18: c.Check(match("'...'\"...\"", re), matches) // Mixed strings
! 19: c.Check(match("\"...\"", re), matches)
! 20: c.Check(match("`cat file`", re), matches)
! 21: c.Check(match("${file%.c}.o", re), matches)
! 22: }
! 23:
! 24: func (s *Suite) TestSplitIntoShellTokens_LineContinuation(c *check.C) {
! 25: line := NewLine("fname", 10, "dummy", nil)
1.1 rillig 26:
1.7 ! rillig 27: words, rest := splitIntoShellTokens(line, "if true; then \\")
1.1 rillig 28:
29: c.Check(words, check.DeepEquals, []string{"if", "true", ";", "then"})
30: c.Check(rest, equals, "\\")
31:
1.7 ! rillig 32: words, rest = splitIntoShellTokens(line, "pax -s /.*~$$//g")
1.1 rillig 33:
34: c.Check(words, check.DeepEquals, []string{"pax", "-s", "/.*~$$//g"})
35: c.Check(rest, equals, "")
36: }
37:
1.7 ! rillig 38: func (s *Suite) TestChecklineMkShellCommandLine(c *check.C) {
1.3 rillig 39: s.UseCommandLine(c, "-Wall")
1.7 ! rillig 40: G.Mk = s.NewMkLines("fname",
! 41: "# dummy")
! 42: shline := NewShellLine(G.Mk.mklines[0])
1.3 rillig 43:
1.7 ! rillig 44: shline.CheckShellCommandLine("@# Comment")
1.3 rillig 45:
46: c.Check(s.Output(), equals, "")
47:
1.7 ! rillig 48: shline.CheckShellCommandLine("uname=`uname`; echo $$uname; echo")
1.3 rillig 49:
50: c.Check(s.Output(), equals, ""+
51: "WARN: fname:1: Unknown shell command \"uname\".\n"+
1.7 ! rillig 52: "WARN: fname:1: Please switch to \"set -e\" mode before using a semicolon (the one after \"uname=`uname`\") to separate commands.\n"+
1.3 rillig 53: "WARN: fname:1: Unknown shell command \"echo\".\n"+
1.7 ! rillig 54: "WARN: fname:1: Unquoted shell variable \"uname\".\n"+
! 55: "WARN: fname:1: Unknown shell command \"echo\".\n")
1.3 rillig 56:
1.7 ! rillig 57: G.globalData.Tools = map[string]bool{"echo": true}
! 58: G.globalData.PredefinedTools = map[string]bool{"echo": true}
! 59: G.Mk = s.NewMkLines("fname",
! 60: "# dummy")
1.3 rillig 61: G.globalData.InitVartypes()
62:
1.7 ! rillig 63: shline.CheckShellCommandLine("echo ${PKGNAME:Q}") // vucQuotPlain
1.3 rillig 64:
65: c.Check(s.Output(), equals, ""+
66: "WARN: fname:1: PKGNAME may not be used in this file.\n"+
67: "NOTE: fname:1: The :Q operator isn't necessary for ${PKGNAME} here.\n")
68:
1.7 ! rillig 69: shline.CheckShellCommandLine("echo \"${CFLAGS:Q}\"") // vucQuotDquot
1.1 rillig 70:
1.3 rillig 71: c.Check(s.Output(), equals, ""+
72: "WARN: fname:1: Please don't use the :Q operator in double quotes.\n"+
73: "WARN: fname:1: CFLAGS may not be used in this file.\n"+
74: "WARN: fname:1: Please use ${CFLAGS:M*:Q} instead of ${CFLAGS:Q} and make sure the variable appears outside of any quoting characters.\n")
75:
1.7 ! rillig 76: shline.CheckShellCommandLine("echo '${COMMENT:Q}'") // vucQuotSquot
1.3 rillig 77:
78: c.Check(s.Output(), equals, "WARN: fname:1: COMMENT may not be used in this file.\n")
1.4 rillig 79:
1.7 ! rillig 80: shline.CheckShellCommandLine("echo $$@")
1.3 rillig 81:
82: c.Check(s.Output(), equals, "WARN: fname:1: The $@ shell variable should only be used in double quotes.\n")
1.4 rillig 83:
1.7 ! rillig 84: shline.CheckShellCommandLine("echo \"$$\"") // As seen by make(1); the shell sees: echo $
! 85:
! 86: c.Check(s.Output(), equals, "WARN: fname:1: Unescaped $ or strange shell variable found.\n")
! 87:
! 88: shline.CheckShellCommandLine("echo \"\\n\"") // As seen by make(1); the shell sees: echo "\n"
! 89:
! 90: c.Check(s.Output(), equals, "")
! 91:
! 92: shline.CheckShellCommandLine("${RUN} for f in *.c; do echo $${f%.c}; done")
! 93:
! 94: c.Check(s.Output(), equals, "")
! 95:
! 96: // Based on mail/thunderbird/Makefile, rev. 1.159
! 97: shline.CheckShellCommandLine("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"")
1.4 rillig 98:
1.7 ! rillig 99: c.Check(s.Output(), equals, ""+
! 100: "WARN: fname:1: Unknown shell command \"unzip\".\n"+
! 101: "WARN: fname:1: The exitcode of the left-hand-side command of the pipe operator is ignored.\n"+
! 102: "WARN: fname:1: Unknown shell command \"awk\".\n")
! 103:
! 104: // From mail/thunderbird/Makefile, rev. 1.159
! 105: shline.CheckShellCommandLine("" +
! 106: "${RUN} for e in ${XPI_FILES}; do " +
! 107: " subdir=\"`${UNZIP_CMD} -c \"$$e\" install.rdf | awk '/^ <em:id>/ {sub(\".*<em:id>\",\"\");sub(\"</em:id>.*\",\"\");print;exit;}'`\" && " +
! 108: " ${MKDIR} \"${WRKDIR}/extensions/$$subdir\" && " +
! 109: " cd \"${WRKDIR}/extensions/$$subdir\" && " +
! 110: " ${UNZIP_CMD} -aqo $$e; " +
! 111: "done")
1.4 rillig 112:
1.7 ! rillig 113: c.Check(s.Output(), equals, ""+
! 114: "WARN: fname:1: XPI_FILES is used but not defined. Spelling mistake?\n"+
! 115: "WARN: fname:1: UNZIP_CMD is used but not defined. Spelling mistake?\n"+
! 116: "WARN: fname:1: The exitcode of the left-hand-side command of the pipe operator is ignored.\n"+
! 117: "WARN: fname:1: Unknown shell command \"awk\".\n"+
! 118: "WARN: fname:1: MKDIR is used but not defined. Spelling mistake?\n"+
! 119: "WARN: fname:1: Unknown shell command \"${MKDIR}\".\n"+
! 120: "WARN: fname:1: UNZIP_CMD is used but not defined. Spelling mistake?\n"+
! 121: "WARN: fname:1: Unquoted shell variable \"e\".\n")
! 122:
! 123: // From x11/wxGTK28/Makefile
! 124: shline.CheckShellCommandLine("" +
! 125: "set -e; cd ${WRKSRC}/locale; " +
! 126: "for lang in *.po; do " +
! 127: " [ \"$${lang}\" = \"wxstd.po\" ] && continue; " +
! 128: " ${TOOLS_PATH.msgfmt} -c -o \"$${lang%.po}.mo\" \"$${lang}\"; " +
! 129: "done")
1.4 rillig 130:
1.7 ! rillig 131: c.Check(s.Output(), equals, ""+
! 132: "WARN: fname:1: WRKSRC may not be used in this file.\n"+
! 133: "WARN: fname:1: Unknown shell command \"[\".\n"+
! 134: "WARN: fname:1: Unknown shell command \"${TOOLS_PATH.msgfmt}\".\n")
! 135:
! 136: shline.CheckShellCommandLine("@cp from to")
! 137:
! 138: c.Check(s.Output(), equals, ""+
! 139: "WARN: fname:1: The shell command \"cp\" should not be hidden.\n"+
! 140: "WARN: fname:1: Unknown shell command \"cp\".\n")
! 141:
! 142: shline.CheckShellCommandLine("${RUN} ${INSTALL_DATA_DIR} share/pkgbase ${PREFIX}/share/pkgbase")
! 143:
! 144: c.Check(s.Output(), equals, "NOTE: fname:1: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= share/pkgbase\" instead of this command.\n")
1.1 rillig 145: }
146:
1.7 ! rillig 147: func (s *Suite) TestShellLine_CheckShelltext_nofix(c *check.C) {
1.6 rillig 148: s.UseCommandLine(c, "-Wall")
149: G.globalData.InitVartypes()
1.7 ! rillig 150: s.RegisterTool("echo", "ECHO", false)
! 151: G.Mk = s.NewMkLines("Makefile",
! 152: "\techo ${PKGNAME:Q}")
! 153: shline := NewShellLine(G.Mk.mklines[0])
! 154:
! 155: c.Check(shline.line.raw[0].textnl, equals, "\techo ${PKGNAME:Q}\n")
! 156: c.Check(shline.line.raw[0].Lineno, equals, 1)
! 157:
! 158: shline.CheckShellCommandLine("echo ${PKGNAME:Q}")
! 159:
! 160: c.Check(s.Output(), equals, ""+
! 161: "NOTE: Makefile:1: The :Q operator isn't necessary for ${PKGNAME} here.\n")
! 162: }
! 163:
! 164: func (s *Suite) TestShellLine_CheckShelltext_showAutofix(c *check.C) {
! 165: s.UseCommandLine(c, "-Wall", "--show-autofix")
! 166: G.globalData.InitVartypes()
! 167: s.RegisterTool("echo", "ECHO", false)
! 168: G.Mk = s.NewMkLines("Makefile",
! 169: "\techo ${PKGNAME:Q}")
! 170: shline := NewShellLine(G.Mk.mklines[0])
! 171:
! 172: shline.CheckShellCommandLine("echo ${PKGNAME:Q}")
! 173:
! 174: c.Check(s.Output(), equals, ""+
! 175: "NOTE: Makefile:1: The :Q operator isn't necessary for ${PKGNAME} here.\n"+
! 176: "AUTOFIX: Makefile:1: Replacing \"${PKGNAME:Q}\" with \"${PKGNAME}\".\n")
! 177: }
! 178:
! 179: func (s *Suite) TestShellLine_CheckShelltext_autofix(c *check.C) {
! 180: s.UseCommandLine(c, "-Wall", "--autofix")
! 181: G.globalData.InitVartypes()
! 182: s.RegisterTool("echo", "ECHO", false)
! 183: G.Mk = s.NewMkLines("Makefile",
! 184: "\techo ${PKGNAME:Q}")
! 185: shline := NewShellLine(G.Mk.mklines[0])
! 186:
! 187: shline.CheckShellCommandLine("echo ${PKGNAME:Q}")
! 188:
! 189: c.Check(s.Output(), equals, ""+
! 190: "AUTOFIX: Makefile:1: Replacing \"${PKGNAME:Q}\" with \"${PKGNAME}\".\n")
! 191: }
! 192:
! 193: func (s *Suite) TestShellLine_CheckShelltext_InternalError1(c *check.C) {
! 194: s.UseCommandLine(c, "-Wall")
! 195: G.globalData.InitVartypes()
! 196: G.Mk = s.NewMkLines("fname",
! 197: "# dummy")
! 198: shline := NewShellLine(G.Mk.mklines[0])
1.6 rillig 199:
200: // foobar="`echo \"foo bar\"`"
1.7 ! rillig 201: shline.CheckShellCommandLine("foobar=\"`echo \\\"foo bar\\\"`\"")
1.6 rillig 202:
203: c.Check(s.Output(), equals, ""+
204: "WARN: fname:1: Backslashes should be doubled inside backticks.\n"+
205: "WARN: fname:1: Double quotes inside backticks inside double quotes are error prone.\n"+
206: "WARN: fname:1: Backslashes should be doubled inside backticks.\n"+
207: "WARN: fname:1: Double quotes inside backticks inside double quotes are error prone.\n"+
208: "WARN: fname:1: Unknown shell command \"echo\".\n"+
1.7 ! rillig 209: "ERROR: fname:1: Internal pkglint error: ShellLine.CheckToken state=plain, rest=\"\\\\foo\", token=\"\\\\foo\"\n"+
! 210: "ERROR: fname:1: Internal pkglint error: ShellLine.CheckShellCommand state=continuation rest=\"\\\\\" shellcmd=\"echo \\\\foo bar\\\\\"\n")
1.6 rillig 211: }
212:
1.7 ! rillig 213: func (s *Suite) TestShellLine_CheckShelltext_DollarWithoutVariable(c *check.C) {
1.6 rillig 214: G.globalData.InitVartypes()
1.7 ! rillig 215: G.Mk = s.NewMkLines("fname",
! 216: "# dummy")
! 217: shline := NewShellLine(G.Mk.mklines[0])
1.6 rillig 218: s.RegisterTool("pax", "PAX", false)
1.7 ! rillig 219: G.Mk.tools["pax"] = true
1.6 rillig 220:
1.7 ! rillig 221: shline.CheckShellCommandLine("pax -rwpp -s /.*~$$//g . ${DESTDIR}${PREFIX}")
1.6 rillig 222:
1.7 ! rillig 223: c.Check(s.Output(), equals, "")
1.6 rillig 224: }
225:
1.1 rillig 226: func (s *Suite) TestChecklineMkShellword(c *check.C) {
1.2 rillig 227: s.UseCommandLine(c, "-Wall")
1.1 rillig 228: G.globalData.InitVartypes()
1.7 ! rillig 229: shline := NewShellLine(NewMkLine(NewLine("fname", 1, "# dummy", nil)))
1.1 rillig 230:
231: c.Check(matches("${list}", `^`+reVarnameDirect+`$`), equals, false)
232:
1.7 ! rillig 233: shline.CheckToken("${${list}}", false)
1.1 rillig 234:
235: c.Check(s.Output(), equals, "")
236:
1.7 ! rillig 237: shline.CheckToken("\"$@\"", false)
1.1 rillig 238:
239: c.Check(s.Output(), equals, "WARN: fname:1: Please use \"${.TARGET}\" instead of \"$@\".\n")
1.7 ! rillig 240:
! 241: shline.CheckToken("${COMMENT:Q}", true)
! 242:
! 243: c.Check(s.Output(), equals, "WARN: fname:1: COMMENT may not be used in this file.\n")
! 244:
! 245: shline.CheckToken("\"${DISTINFO_FILE:Q}\"", true)
! 246:
! 247: c.Check(s.Output(), equals, ""+
! 248: "WARN: fname:1: DISTINFO_FILE may not be used in this file.\n"+
! 249: "NOTE: fname:1: The :Q operator isn't necessary for ${DISTINFO_FILE} here.\n")
! 250:
! 251: shline.CheckToken("embed${DISTINFO_FILE:Q}ded", true)
! 252:
! 253: c.Check(s.Output(), equals, ""+
! 254: "WARN: fname:1: DISTINFO_FILE may not be used in this file.\n"+
! 255: "NOTE: fname:1: The :Q operator isn't necessary for ${DISTINFO_FILE} here.\n")
! 256:
! 257: shline.CheckToken("s,\\.,,", true)
! 258:
! 259: c.Check(s.Output(), equals, "")
! 260:
! 261: shline.CheckToken("\"s,\\.,,\"", true)
! 262:
! 263: c.Check(s.Output(), equals, "")
1.1 rillig 264: }
265:
1.7 ! rillig 266: func (s *Suite) TestShellLine_CheckToken_DollarWithoutVariable(c *check.C) {
! 267: shline := NewShellLine(NewMkLine(NewLine("fname", 1, "# dummy", nil)))
1.6 rillig 268:
1.7 ! rillig 269: shline.CheckToken("/.*~$$//g", false) // Typical argument to pax(1).
1.6 rillig 270:
1.7 ! rillig 271: c.Check(s.Output(), equals, "")
1.6 rillig 272: }
273:
1.1 rillig 274: func (s *Suite) TestShelltextContext_CheckCommandStart(c *check.C) {
1.2 rillig 275: s.UseCommandLine(c, "-Wall")
1.6 rillig 276: s.RegisterTool("echo", "ECHO", true)
1.7 ! rillig 277: G.Mk = s.NewMkLines("fname",
! 278: "# dummy")
! 279: mkline := NewMkLine(NewLine("fname", 3, "# dummy", nil))
! 280:
! 281: mkline.CheckText("echo \"hello, world\"")
1.1 rillig 282:
1.7 ! rillig 283: c.Check(s.Output(), equals, "")
! 284:
! 285: NewShellLine(mkline).CheckShellCommandLine("echo \"hello, world\"")
1.1 rillig 286:
287: c.Check(s.Output(), equals, ""+
288: "WARN: fname:3: Please use \"${ECHO}\" instead of \"echo\".\n")
289: }
1.3 rillig 290:
1.7 ! rillig 291: func (s *Suite) TestShellLine_checklineMkShelltext(c *check.C) {
1.3 rillig 292:
1.7 ! rillig 293: shline := NewShellLine(NewMkLine(NewLine("Makefile", 3, "# dummy", nil)))
1.3 rillig 294:
1.7 ! rillig 295: shline.CheckShellCommandLine("for f in *.pl; do ${SED} s,@PREFIX@,${PREFIX}, < $f > $f.tmp && ${MV} $f.tmp $f; done")
1.3 rillig 296:
1.5 rillig 297: c.Check(s.Output(), equals, "NOTE: Makefile:3: Please use the SUBST framework instead of ${SED} and ${MV}.\n")
1.3 rillig 298:
1.7 ! rillig 299: shline.CheckShellCommandLine("install -c manpage.1 ${PREFIX}/man/man1/manpage.1")
1.3 rillig 300:
1.5 rillig 301: c.Check(s.Output(), equals, "WARN: Makefile:3: Please use ${PKGMANDIR} instead of \"man\".\n")
1.3 rillig 302:
1.7 ! rillig 303: shline.CheckShellCommandLine("cp init-script ${PREFIX}/etc/rc.d/service")
1.3 rillig 304:
1.5 rillig 305: c.Check(s.Output(), equals, "WARN: Makefile:3: Please use the RCD_SCRIPTS mechanism to install rc.d scripts automatically to ${RCD_SCRIPTS_EXAMPLEDIR}.\n")
1.3 rillig 306: }
307:
1.7 ! rillig 308: func (s *Suite) TestShellLine_checkCommandUse(c *check.C) {
! 309: G.Mk = s.NewMkLines("fname",
! 310: "# dummy")
! 311: G.Mk.target = "do-install"
1.3 rillig 312:
1.7 ! rillig 313: shline := NewShellLine(NewMkLine(NewLine("fname", 1, "\tdummy", nil)))
1.3 rillig 314:
315: shline.checkCommandUse("sed")
316:
317: c.Check(s.Output(), equals, "WARN: fname:1: The shell command \"sed\" should not be used in the install phase.\n")
318:
319: shline.checkCommandUse("cp")
320:
321: c.Check(s.Output(), equals, "WARN: fname:1: ${CP} should not be used to install files.\n")
322: }
1.7 ! rillig 323:
! 324: func (s *Suite) TestSplitIntoShellWords(c *check.C) {
! 325: url := "http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file="
! 326:
! 327: words, rest := splitIntoShellTokens(dummyLine, url) // Doesn’t really make sense
! 328:
! 329: c.Check(words, check.DeepEquals, []string{"http://registry.gimp.org/file/fix-ca.c?action=download", "&", "id=9884", "&", "file="})
! 330: c.Check(rest, equals, "")
! 331:
! 332: words, rest = splitIntoShellWords(dummyLine, url)
! 333:
! 334: c.Check(words, check.DeepEquals, []string{"http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file="})
! 335: c.Check(rest, equals, "")
! 336:
! 337: words, rest = splitIntoShellWords(dummyLine, "a b \"c c c\" d;;d;; \"e\"''`` 'rest")
! 338:
! 339: c.Check(words, check.DeepEquals, []string{"a", "b", "\"c c c\"", "d;;d;;", "\"e\"''``"})
! 340: c.Check(rest, equals, "'rest")
! 341: }
! 342:
! 343: func (s *Suite) TestShellLine_CheckShellCommandLine_SedMv(c *check.C) {
! 344: shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} ${SED} 's,#,// comment:,g' fname > fname.tmp; ${MV} fname.tmp fname", nil)))
! 345:
! 346: shline.CheckShellCommandLine(shline.mkline.Shellcmd())
! 347:
! 348: c.Check(s.Output(), equals, "NOTE: Makefile:85: Please use the SUBST framework instead of ${SED} and ${MV}.\n")
! 349: }
! 350:
! 351: func (s *Suite) TestShellLine_CheckShellCommandLine_Subshell(c *check.C) {
! 352: shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} uname=$$(uname)", nil)))
! 353:
! 354: shline.CheckShellCommandLine(shline.mkline.Shellcmd())
! 355:
! 356: c.Check(s.Output(), equals, "WARN: Makefile:85: Invoking subshells via $(...) is not portable enough.\n")
! 357: }
! 358:
! 359: func (s *Suite) TestShellLine_CheckShellCommandLine_InstallDirs(c *check.C) {
! 360: shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} ${INSTALL_DATA_DIR} ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2", nil)))
! 361:
! 362: shline.CheckShellCommandLine(shline.mkline.Shellcmd())
! 363:
! 364: c.Check(s.Output(), equals, ""+
! 365: "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= dir1\" instead of this command.\n"+
! 366: "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= dir2\" instead of this command.\n"+
! 367: "WARN: Makefile:85: The INSTALL_*_DIR commands can only handle one directory at a time.\n")
! 368: }
! 369:
! 370: func (s *Suite) TestShellLine_CheckShellCommandLine_InstallD(c *check.C) {
! 371: shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} ${INSTALL} -d ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2", nil)))
! 372:
! 373: shline.CheckShellCommandLine(shline.mkline.Shellcmd())
! 374:
! 375: c.Check(s.Output(), equals, ""+
! 376: "WARN: Makefile:85: Please use AUTO_MKDIRS instead of \"${INSTALL} -d\".\n"+
! 377: "WARN: Makefile:85: Please use AUTO_MKDIRS instead of \"${INSTALL} -d\".\n")
! 378: }
! 379:
! 380: func (s *Suite) TestShellLine_(c *check.C) {
! 381: tmpfile := s.CreateTmpFile(c, "Makefile", ""+
! 382: "# $"+"NetBSD$\n"+
! 383: "pre-install:\n"+
! 384: "\t"+"# comment\\\n"+
! 385: "\t"+"echo \"hello\"\n")
! 386: lines := LoadNonemptyLines(tmpfile, true)
! 387:
! 388: NewMkLines(lines).Check()
! 389:
! 390: c.Check(s.OutputCleanTmpdir(), equals, "WARN: ~/Makefile:3--4: A shell comment does not stop at the end of line.\n")
! 391: }
CVSweb <webmaster@jp.NetBSD.org>