[BACK]Return to shell_test.go CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / pkgsrc / pkgtools / pkglint / files

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>