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

Annotation of pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go, Revision 1.13

1.12      rillig      1: package pkglint
1.1       rillig      2:
1.8       rillig      3: import (
                      4:        "gopkg.in/check.v1"
                      5:        "strings"
                      6: )
1.1       rillig      7:
                      8: func (s *Suite) Test_ShTokenizer_ShAtom(c *check.C) {
1.6       rillig      9:        t := s.Init(c)
                     10:
1.13    ! rillig     11:        // testRest ensures that the given string is parsed to the expected
1.8       rillig     12:        // atoms, and returns the remaining text.
1.13    ! rillig     13:        testRest := func(s string, expected ...*ShAtom) string {
1.1       rillig     14:                p := NewShTokenizer(dummyLine, s, false)
                     15:                q := shqPlain
                     16:                for _, exp := range expected {
                     17:                        c.Check(p.ShAtom(q), deepEquals, exp)
                     18:                        q = exp.Quoting
                     19:                }
                     20:                return p.Rest()
                     21:        }
1.8       rillig     22:
1.11      rillig     23:        // test ensures that the given string is parsed to the expected
1.8       rillig     24:        // atoms, and that the text is completely consumed by the parser.
1.11      rillig     25:        test := func(str string, expected ...*ShAtom) {
1.13    ! rillig     26:                rest := testRest(str, expected...)
1.1       rillig     27:                c.Check(rest, equals, "")
1.6       rillig     28:                t.CheckOutputEmpty()
1.1       rillig     29:        }
                     30:
1.8       rillig     31:        atom := func(typ ShAtomType, text string) *ShAtom {
                     32:                return &ShAtom{typ, text, shqPlain, nil}
1.1       rillig     33:        }
1.8       rillig     34:
                     35:        operator := func(s string) *ShAtom { return atom(shtOperator, s) }
                     36:        comment := func(s string) *ShAtom { return atom(shtComment, s) }
                     37:        mkvar := func(varname string, modifiers ...string) *ShAtom {
1.1       rillig     38:                text := "${" + varname
                     39:                for _, modifier := range modifiers {
1.8       rillig     40:                        text += ":" + strings.Replace(strings.Replace(modifier, "\\", "\\\\", -1), ":", "\\:", -1)
1.1       rillig     41:                }
                     42:                text += "}"
1.10      rillig     43:                varuse := NewMkVarUse(varname, modifiers...)
1.1       rillig     44:                return &ShAtom{shtVaruse, text, shqPlain, varuse}
                     45:        }
1.11      rillig     46:        shvar := func(text, varname string) *ShAtom { return &ShAtom{shtShVarUse, text, shqPlain, varname} }
                     47:        text := func(s string) *ShAtom { return atom(shtText, s) }
1.8       rillig     48:        whitespace := func(s string) *ShAtom { return atom(shtSpace, s) }
                     49:
                     50:        space := whitespace(" ")
1.3       rillig     51:        semicolon := operator(";")
                     52:        pipe := operator("|")
1.8       rillig     53:        subshell := atom(shtSubshell, "$$(")
                     54:
                     55:        q := func(q ShQuoting, atom *ShAtom) *ShAtom {
                     56:                return &ShAtom{atom.Type, atom.MkText, q, atom.data}
                     57:        }
                     58:        backt := func(atom *ShAtom) *ShAtom { return q(shqBackt, atom) }
                     59:        dquot := func(atom *ShAtom) *ShAtom { return q(shqDquot, atom) }
                     60:        squot := func(atom *ShAtom) *ShAtom { return q(shqSquot, atom) }
                     61:        subsh := func(atom *ShAtom) *ShAtom { return q(shqSubsh, atom) }
                     62:        backtDquot := func(atom *ShAtom) *ShAtom { return q(shqBacktDquot, atom) }
                     63:        backtSquot := func(atom *ShAtom) *ShAtom { return q(shqBacktSquot, atom) }
                     64:        dquotBackt := func(atom *ShAtom) *ShAtom { return q(shqDquotBackt, atom) }
                     65:        subshDquot := func(atom *ShAtom) *ShAtom { return q(shqSubshDquot, atom) }
                     66:        subshSquot := func(atom *ShAtom) *ShAtom { return q(shqSubshSquot, atom) }
                     67:        dquotBacktDquot := func(atom *ShAtom) *ShAtom { return q(shqDquotBacktDquot, atom) }
                     68:        dquotBacktSquot := func(atom *ShAtom) *ShAtom { return q(shqDquotBacktSquot, atom) }
                     69:
                     70:        // Ignore unused functions; useful for deleting some of the tests during debugging.
                     71:        use := func(args ...interface{}) {}
1.13    ! rillig     72:        use(testRest, test)
1.8       rillig     73:        use(operator, comment, mkvar, text, whitespace)
                     74:        use(space, semicolon, pipe, subshell)
                     75:        use(backt, dquot, squot, subsh)
                     76:        use(backtDquot, backtSquot, dquotBackt, subshDquot, subshSquot)
                     77:        use(dquotBacktDquot, dquotBacktSquot)
1.1       rillig     78:
1.11      rillig     79:        test("" /* none */)
1.1       rillig     80:
1.11      rillig     81:        test("$$var",
                     82:                shvar("$$var", "var"))
1.1       rillig     83:
1.11      rillig     84:        test("$$var$$var",
                     85:                shvar("$$var", "var"),
                     86:                shvar("$$var", "var"))
1.1       rillig     87:
1.11      rillig     88:        test("$$var;;",
                     89:                shvar("$$var", "var"),
1.3       rillig     90:                operator(";;"))
1.1       rillig     91:
1.11      rillig     92:        test("'single-quoted'",
1.8       rillig     93:                squot(text("'")),
                     94:                squot(text("single-quoted")),
                     95:                text("'"))
1.1       rillig     96:
1.13    ! rillig     97:        rest := testRest("\"" /* none */)
1.1       rillig     98:        c.Check(rest, equals, "\"")
                     99:
1.11      rillig    100:        test("$${file%.c}.o",
                    101:                shvar("$${file%.c}", "file"),
                    102:                text(".o"))
1.1       rillig    103:
1.11      rillig    104:        test("hello",
1.8       rillig    105:                text("hello"))
1.1       rillig    106:
1.11      rillig    107:        test("hello, world",
1.8       rillig    108:                text("hello,"),
1.1       rillig    109:                space,
1.8       rillig    110:                text("world"))
1.1       rillig    111:
1.11      rillig    112:        test("\"",
1.8       rillig    113:                dquot(text("\"")))
1.1       rillig    114:
1.11      rillig    115:        test("`",
1.8       rillig    116:                backt(text("`")))
1.1       rillig    117:
1.11      rillig    118:        test("`cat filename`",
1.8       rillig    119:                backt(text("`")),
                    120:                backt(text("cat")),
                    121:                backt(space),
1.11      rillig    122:                backt(text("filename")),
1.8       rillig    123:                text("`"))
1.1       rillig    124:
1.11      rillig    125:        test("hello, \"world\"",
1.8       rillig    126:                text("hello,"),
1.1       rillig    127:                space,
1.8       rillig    128:                dquot(text("\"")),
                    129:                dquot(text("world")),
                    130:                text("\""))
1.1       rillig    131:
1.11      rillig    132:        test("set -e;",
1.8       rillig    133:                text("set"),
1.1       rillig    134:                space,
1.8       rillig    135:                text("-e"),
1.1       rillig    136:                semicolon)
                    137:
1.11      rillig    138:        test("cd ${WRKSRC}/doc/man/man3; PAGES=\"`ls -1 | ${SED} -e 's,3qt$$,3,'`\";",
1.8       rillig    139:                text("cd"),
1.1       rillig    140:                space,
1.8       rillig    141:                mkvar("WRKSRC"),
                    142:                text("/doc/man/man3"),
1.1       rillig    143:                semicolon,
                    144:                space,
1.8       rillig    145:                text("PAGES="),
                    146:                dquot(text("\"")),
                    147:                dquotBackt(text("`")),
                    148:                dquotBackt(text("ls")),
                    149:                dquotBackt(space),
                    150:                dquotBackt(text("-1")),
                    151:                dquotBackt(space),
                    152:                dquotBackt(operator("|")),
                    153:                dquotBackt(space),
                    154:                dquotBackt(mkvar("SED")),
                    155:                dquotBackt(space),
                    156:                dquotBackt(text("-e")),
                    157:                dquotBackt(space),
                    158:                dquotBacktSquot(text("'")),
                    159:                dquotBacktSquot(text("s,3qt$$,3,")),
                    160:                dquotBackt(text("'")),
                    161:                dquot(text("`")),
                    162:                text("\""),
1.1       rillig    163:                semicolon)
                    164:
1.11      rillig    165:        test("ls -1 | ${SED} -e 's,3qt$$,3,'",
1.8       rillig    166:                text("ls"), space, text("-1"), space,
1.1       rillig    167:                pipe, space,
1.8       rillig    168:                mkvar("SED"), space, text("-e"), space,
                    169:                squot(text("'")), squot(text("s,3qt$$,3,")), text("'"))
1.1       rillig    170:
1.11      rillig    171:        test("(for PAGE in $$PAGES; do ",
1.8       rillig    172:                operator("("),
                    173:                text("for"),
1.1       rillig    174:                space,
1.8       rillig    175:                text("PAGE"),
1.1       rillig    176:                space,
1.8       rillig    177:                text("in"),
1.1       rillig    178:                space,
1.11      rillig    179:                shvar("$$PAGES", "PAGES"),
1.1       rillig    180:                semicolon,
                    181:                space,
1.8       rillig    182:                text("do"),
1.1       rillig    183:                space)
                    184:
1.11      rillig    185:        test("    ${ECHO} installing ${DESTDIR}${QTPREFIX}/man/man3/$${PAGE}; ",
1.1       rillig    186:                whitespace("    "),
1.8       rillig    187:                mkvar("ECHO"),
1.1       rillig    188:                space,
1.8       rillig    189:                text("installing"),
1.1       rillig    190:                space,
1.8       rillig    191:                mkvar("DESTDIR"),
                    192:                mkvar("QTPREFIX"),
1.11      rillig    193:                text("/man/man3/"),
                    194:                shvar("$${PAGE}", "PAGE"),
1.1       rillig    195:                semicolon,
                    196:                space)
                    197:
1.11      rillig    198:        test("    set - X `head -1 $${PAGE}qt`; ",
1.1       rillig    199:                whitespace("    "),
1.8       rillig    200:                text("set"),
1.1       rillig    201:                space,
1.8       rillig    202:                text("-"),
1.1       rillig    203:                space,
1.8       rillig    204:                text("X"),
1.1       rillig    205:                space,
1.8       rillig    206:                backt(text("`")),
                    207:                backt(text("head")),
                    208:                backt(space),
                    209:                backt(text("-1")),
                    210:                backt(space),
1.11      rillig    211:                backt(shvar("$${PAGE}", "PAGE")),
                    212:                backt(text("qt")),
1.8       rillig    213:                text("`"),
1.1       rillig    214:                semicolon,
                    215:                space)
                    216:
1.11      rillig    217:        test("`\"one word\"`",
1.8       rillig    218:                backt(text("`")),
                    219:                backtDquot(text("\"")),
                    220:                backtDquot(text("one word")),
                    221:                backt(text("\"")),
                    222:                text("`"))
1.1       rillig    223:
1.11      rillig    224:        test("$$var \"$$var\" '$$var' `$$var`",
                    225:                shvar("$$var", "var"),
1.1       rillig    226:                space,
1.8       rillig    227:                dquot(text("\"")),
1.11      rillig    228:                dquot(shvar("$$var", "var")),
1.8       rillig    229:                text("\""),
                    230:                space,
                    231:                squot(text("'")),
1.11      rillig    232:                squot(shvar("$$var", "var")),
1.8       rillig    233:                text("'"),
                    234:                space,
                    235:                backt(text("`")),
1.11      rillig    236:                backt(shvar("$$var", "var")),
1.8       rillig    237:                text("`"))
1.1       rillig    238:
1.11      rillig    239:        test("\"`'echo;echo'`\"",
1.8       rillig    240:                dquot(text("\"")),
                    241:                dquotBackt(text("`")),
                    242:                dquotBacktSquot(text("'")),
                    243:                dquotBacktSquot(text("echo;echo")),
                    244:                dquotBackt(text("'")),
                    245:                dquot(text("`")),
                    246:                text("\""))
1.1       rillig    247:
1.11      rillig    248:        test("cat<file",
1.8       rillig    249:                text("cat"),
1.3       rillig    250:                operator("<"),
1.8       rillig    251:                text("file"))
1.1       rillig    252:
1.11      rillig    253:        test("\\$$escaped",
                    254:                text("\\$$escaped"))
                    255:
                    256:        test("-e \"s,\\$$sysconfdir/jabberd,\\$$sysconfdir,g\"",
1.8       rillig    257:                text("-e"),
1.1       rillig    258:                space,
1.8       rillig    259:                dquot(text("\"")),
                    260:                dquot(text("s,\\$$sysconfdir/jabberd,\\$$sysconfdir,g")),
                    261:                text("\""))
1.1       rillig    262:
1.11      rillig    263:        test("echo $$, $$- $$/ $$; $$| $$,$$/$$;$$-",
1.8       rillig    264:                text("echo"),
1.1       rillig    265:                space,
1.8       rillig    266:                text("$$,"),
1.7       rillig    267:                space,
1.11      rillig    268:                shvar("$$-", "-"),
1.7       rillig    269:                space,
1.8       rillig    270:                text("$$/"),
1.7       rillig    271:                space,
1.8       rillig    272:                text("$$"),
1.7       rillig    273:                semicolon,
                    274:                space,
1.8       rillig    275:                text("$$"),
1.7       rillig    276:                pipe,
                    277:                space,
1.8       rillig    278:                text("$$,$$/$$"),
1.7       rillig    279:                semicolon,
1.11      rillig    280:                shvar("$$-", "-"))
1.1       rillig    281:
1.13    ! rillig    282:        rest = testRest("COMMENT=\t\\Make $$$$ fast\"",
1.8       rillig    283:                text("COMMENT="),
1.1       rillig    284:                whitespace("\t"),
1.8       rillig    285:                text("\\Make"),
1.1       rillig    286:                space,
1.11      rillig    287:                shvar("$$$$", "$"),
1.1       rillig    288:                space,
1.8       rillig    289:                text("fast"))
1.1       rillig    290:        c.Check(rest, equals, "\"")
                    291:
1.11      rillig    292:        test("var=`echo;echo|echo&echo||echo&&echo>echo`",
1.8       rillig    293:                text("var="),
                    294:                backt(text("`")),
                    295:                backt(text("echo")),
                    296:                backt(semicolon),
                    297:                backt(text("echo")),
                    298:                backt(operator("|")),
                    299:                backt(text("echo")),
                    300:                backt(operator("&")),
                    301:                backt(text("echo")),
                    302:                backt(operator("||")),
                    303:                backt(text("echo")),
                    304:                backt(operator("&&")),
                    305:                backt(text("echo")),
                    306:                backt(operator(">")),
                    307:                backt(text("echo")),
                    308:                text("`"))
1.1       rillig    309:
1.11      rillig    310:        test("# comment",
1.8       rillig    311:                comment("# comment"))
1.11      rillig    312:        test("no#comment",
1.8       rillig    313:                text("no#comment"))
1.11      rillig    314:        test("`# comment`continue",
1.8       rillig    315:                backt(text("`")),
                    316:                backt(comment("# comment")),
                    317:                text("`"),
                    318:                text("continue"))
1.11      rillig    319:        test("`no#comment`continue",
1.8       rillig    320:                backt(text("`")),
                    321:                backt(text("no#comment")),
                    322:                text("`"),
                    323:                text("continue"))
1.1       rillig    324:
1.11      rillig    325:        test("var=`tr 'A-Z' 'a-z'`",
1.8       rillig    326:                text("var="),
                    327:                backt(text("`")),
                    328:                backt(text("tr")),
                    329:                backt(space),
                    330:                backtSquot(text("'")),
                    331:                backtSquot(text("A-Z")),
                    332:                backt(text("'")),
                    333:                backt(space),
                    334:                backtSquot(text("'")),
                    335:                backtSquot(text("a-z")),
                    336:                backt(text("'")),
                    337:                text("`"))
1.1       rillig    338:
1.11      rillig    339:        test("var=\"`echo \"\\`echo foo\\`\"`\"",
1.8       rillig    340:                text("var="),
                    341:                dquot(text("\"")),
                    342:                dquotBackt(text("`")),
                    343:                dquotBackt(text("echo")),
                    344:                dquotBackt(space),
                    345:                dquotBacktDquot(text("\"")),
                    346:                dquotBacktDquot(text("\\`echo foo\\`")), // One atom, since it doesn't influence parsing.
                    347:                dquotBackt(text("\"")),
                    348:                dquot(text("`")),
                    349:                text("\""))
1.1       rillig    350:
1.11      rillig    351:        test("if cond1; then action1; elif cond2; then action2; else action3; fi",
1.8       rillig    352:                text("if"), space, text("cond1"), semicolon, space,
                    353:                text("then"), space, text("action1"), semicolon, space,
                    354:                text("elif"), space, text("cond2"), semicolon, space,
                    355:                text("then"), space, text("action2"), semicolon, space,
                    356:                text("else"), space, text("action3"), semicolon, space,
                    357:                text("fi"))
                    358:
1.11      rillig    359:        test("$$(cat)",
1.8       rillig    360:                subsh(subshell),
                    361:                subsh(text("cat")),
                    362:                text(")"))
                    363:
1.11      rillig    364:        test("$$(cat 'file')",
1.8       rillig    365:                subsh(subshell),
                    366:                subsh(text("cat")),
                    367:                subsh(space),
                    368:                subshSquot(text("'")),
                    369:                subshSquot(text("file")),
                    370:                subsh(text("'")),
                    371:                text(")"))
                    372:
1.11      rillig    373:        test("$$(# comment) arg",
1.8       rillig    374:                subsh(subshell),
                    375:                subsh(comment("# comment")),
                    376:                text(")"),
                    377:                space,
                    378:                text("arg"))
                    379:
1.11      rillig    380:        test("$$(echo \"first\" 'second')",
1.8       rillig    381:                subsh(subshell),
                    382:                subsh(text("echo")),
                    383:                subsh(space),
                    384:                subshDquot(text("\"")),
                    385:                subshDquot(text("first")),
                    386:                subsh(text("\"")),
                    387:                subsh(space),
                    388:                subshSquot(text("'")),
                    389:                subshSquot(text("second")),
                    390:                subsh(text("'")),
                    391:                text(")"))
1.1       rillig    392: }
                    393:
1.8       rillig    394: func (s *Suite) Test_ShTokenizer_ShAtom__quoting(c *check.C) {
1.13    ! rillig    395:        test := func(input, expectedOutput string) {
1.1       rillig    396:                p := NewShTokenizer(dummyLine, input, false)
                    397:                q := shqPlain
                    398:                result := ""
                    399:                for {
                    400:                        token := p.ShAtom(q)
                    401:                        if token == nil {
                    402:                                break
                    403:                        }
1.2       rillig    404:                        result += token.MkText
1.1       rillig    405:                        if token.Quoting != q {
                    406:                                q = token.Quoting
                    407:                                result += "[" + q.String() + "]"
                    408:                        }
                    409:                }
                    410:                c.Check(result, equals, expectedOutput)
                    411:                c.Check(p.Rest(), equals, "")
                    412:        }
                    413:
1.13    ! rillig    414:        test("hello, world", "hello, world")
        !           415:        test("hello, \"world\"", "hello, \"[d]world\"[plain]")
        !           416:        test("1 \"\" 2 '' 3 `` 4", "1 \"[d]\"[plain] 2 '[s]'[plain] 3 `[b]`[plain] 4")
        !           417:        test("\"\"", "\"[d]\"[plain]")
        !           418:        test("''", "'[s]'[plain]")
        !           419:        test("``", "`[b]`[plain]")
        !           420:        test("x\"x`x`x\"x'x\"x'", "x\"[d]x`[db]x`[d]x\"[plain]x'[s]x\"x'[plain]")
        !           421:        test("x\"x`x'x'x`x\"", "x\"[d]x`[db]x'[dbs]x'[db]x`[d]x\"[plain]")
        !           422:        test("x\\\"x\\'x\\`x\\\\", "x\\\"x\\'x\\`x\\\\")
        !           423:        test("x\"x\\\"x\\'x\\`x\\\\", "x\"[d]x\\\"x\\'x\\`x\\\\")
        !           424:        test("x'x\\\"x\\'x\\`x\\\\", "x'[s]x\\\"x\\'[plain]x\\`x\\\\")
        !           425:        test("x`x\\\"x\\'x\\`x\\\\", "x`[b]x\\\"x\\'x\\`x\\\\")
1.1       rillig    426: }
                    427:
                    428: func (s *Suite) Test_ShTokenizer_ShToken(c *check.C) {
1.6       rillig    429:        t := s.Init(c)
                    430:
1.8       rillig    431:        // testRest ensures that the given string is parsed to the expected
                    432:        // tokens, and returns the remaining text.
                    433:        testRest := func(str string, expected ...string) string {
                    434:                p := NewShTokenizer(dummyLine, str, false)
                    435:                for _, exp := range expected {
                    436:                        c.Check(p.ShToken().MkText, equals, exp)
                    437:                }
                    438:                return p.Rest()
                    439:        }
1.13    ! rillig    440:
1.8       rillig    441:        test := func(str string, expected ...string) {
1.1       rillig    442:                p := NewShTokenizer(dummyLine, str, false)
                    443:                for _, exp := range expected {
1.8       rillig    444:                        c.Check(p.ShToken().MkText, equals, exp)
1.1       rillig    445:                }
                    446:                c.Check(p.Rest(), equals, "")
1.6       rillig    447:                t.CheckOutputEmpty()
1.1       rillig    448:        }
1.13    ! rillig    449:
        !           450:        testNil := func(str string) {
1.8       rillig    451:                p := NewShTokenizer(dummyLine, str, false)
                    452:                c.Check(p.ShToken(), check.IsNil)
                    453:                c.Check(p.Rest(), equals, "")
                    454:                t.CheckOutputEmpty()
                    455:        }
                    456:
1.13    ! rillig    457:        testNil("")
        !           458:        testNil(" ")
1.8       rillig    459:        rest := testRest("\t\t\t\n\n\n\n\t ",
1.13    ! rillig    460:                "\n\n\n\n")
        !           461:        c.Check(rest, equals, "\t ")
1.8       rillig    462:
                    463:        test("echo",
                    464:                "echo")
                    465:
                    466:        test("`cat file`",
                    467:                "`cat file`")
                    468:
                    469:        test("PAGES=\"`ls -1 | ${SED} -e 's,3qt$$,3,'`\"",
                    470:                "PAGES=\"`ls -1 | ${SED} -e 's,3qt$$,3,'`\"")
                    471:
                    472:        test("echo hello, world",
                    473:                "echo",
                    474:                "hello,",
                    475:                "world")
                    476:
                    477:        test("if cond1; then action1; elif cond2; then action2; else action3; fi",
                    478:                "if", "cond1", ";", "then",
                    479:                "action1", ";",
                    480:                "elif", "cond2", ";", "then",
                    481:                "action2", ";",
                    482:                "else", "action3", ";",
                    483:                "fi")
                    484:
                    485:        test("PATH=/nonexistent env PATH=${PATH:Q} true",
                    486:                "PATH=/nonexistent",
                    487:                "env",
                    488:                "PATH=${PATH:Q}",
                    489:                "true")
                    490:
                    491:        test("id=$$(${AWK} '{print}' < ${WRKSRC}/idfile)",
                    492:                "id=$$(${AWK} '{print}' < ${WRKSRC}/idfile)")
                    493:
                    494:        test("id=`${AWK} '{print}' < ${WRKSRC}/idfile`",
                    495:                "id=`${AWK} '{print}' < ${WRKSRC}/idfile`")
                    496: }
                    497:
1.11      rillig    498: func (s *Suite) Test_ShTokenizer_shVarUse(c *check.C) {
                    499:
                    500:        test := func(input string, output *ShAtom, rest string) {
                    501:                tok := NewShTokenizer(nil, input, false)
                    502:                actual := tok.shVarUse(shqPlain)
                    503:
                    504:                c.Check(actual, deepEquals, output)
                    505:                c.Check(tok.Rest(), equals, rest)
                    506:        }
                    507:
                    508:        shvar := func(text, varname string) *ShAtom {
                    509:                return &ShAtom{shtShVarUse, text, shqPlain, varname}
                    510:        }
                    511:
                    512:        test("$", nil, "$")
                    513:        test("$$", nil, "$$")
                    514:        test("${MKVAR}", nil, "${MKVAR}")
                    515:
                    516:        test("$$a", shvar("$$a", "a"), "")
                    517:        test("$$a.", shvar("$$a", "a"), ".")
                    518:        test("$$a_b_123:", shvar("$$a_b_123", "a_b_123"), ":")
                    519:        test("$$123", shvar("$$1", "1"), "23")
                    520:
                    521:        test("$${varname}", shvar("$${varname}", "varname"), "")
                    522:        test("$${varname}.", shvar("$${varname}", "varname"), ".")
                    523:        test("$${0123}.", shvar("$${0123}", "0123"), ".")
                    524:        test("$${varname", nil, "$${varname")
                    525:
                    526:        test("$${var:=value}", shvar("$${var:=value}", "var"), "")
                    527:        test("$${var#value}", shvar("$${var#value}", "var"), "")
                    528:        test("$${var##value}", shvar("$${var##value}", "var"), "")
                    529:        test("$${var##*}", shvar("$${var##*}", "var"), "")
                    530:        test("$${var%\".gz\"}", shvar("$${var%\".gz\"}", "var"), "")
                    531:
                    532:        // TODO: allow variables in patterns.
                    533:        test("$${var%.${ext}}", nil, "$${var%.${ext}}")
                    534:
                    535:        test("$${var##*", nil, "$${var##*")
                    536:        test("$${var\"", nil, "$${var\"")
                    537:
                    538:        // TODO: test("$${var%${EXT}}", shvar("$${var%${EXT}}", "var"), "")
                    539:        test("$${var%${EXT}}", nil, "$${var%${EXT}}")
                    540:
                    541:        // TODO: length of var
                    542:        test("$${#var}", nil, "$${#var}")
                    543:
                    544:        test("$${/}", nil, "$${/}")
                    545:        test("$${\\}", nil, "$${\\}")
                    546: }
                    547:
1.13    ! rillig    548: // This test demonstrates that the shell tokenizer is not perfect yet.
        !           549: // There are still some corner cases that trigger a parse error.
        !           550: // To get 100% code coverage, they have been found using the fuzzer
        !           551: // and trimmed down to minimal examples.
1.8       rillig    552: func (s *Suite) Test_ShTokenizer__examples_from_fuzzing(c *check.C) {
                    553:        t := s.Init(c)
1.1       rillig    554:
1.8       rillig    555:        mklines := t.NewMkLines("fuzzing.mk",
                    556:                MkRcsID,
                    557:                "",
                    558:                "pre-configure:",
1.1       rillig    559:
1.8       rillig    560:                // Covers shAtomBacktDquot: return nil.
                    561:                // These are nested backticks with double quotes,
                    562:                // which should be avoided since POSIX marks them as unspecified.
                    563:                "\t"+"`\"`",
1.1       rillig    564:
1.8       rillig    565:                // Covers shAtomBacktSquot: return nil
                    566:                "\t"+"`'$`",
                    567:
                    568:                // Covers shAtomDquotBacktSquot: return nil
                    569:                "\t"+"\"`'`y",
                    570:
                    571:                // Covers shAtomDquotBackt: return nil
1.9       rillig    572:                // FIXME: Pkglint must parse unescaped dollar in the same way, everywhere.
1.8       rillig    573:                "\t"+"\"`$|",
                    574:
                    575:                // Covers shAtomDquotBacktDquot: return nil
                    576:                // FIXME: Pkglint must support unlimited nesting.
                    577:                "\t"+"\"`\"`",
                    578:
                    579:                // Covers shAtomSubshDquot: return nil
                    580:                "\t"+"$$(\"'",
                    581:
1.11      rillig    582:                // Covers shAtomSubsh: case lexer.AdvanceStr("`")
1.8       rillig    583:                "\t"+"$$(`",
                    584:
                    585:                // Covers shAtomSubshSquot: return nil
                    586:                "\t"+"$$('$)",
                    587:
1.11      rillig    588:                // Covers shAtomDquotBackt: case lexer.AdvanceRegexp("^#[^`]*")
1.8       rillig    589:                "\t"+"\"`# comment")
                    590:
                    591:        mklines.Check()
                    592:
                    593:        // Just good that these redundant error messages don't occur every day.
                    594:        t.CheckOutputLines(
1.12      rillig    595:                "WARN: fuzzing.mk:4: Internal pkglint error in ShTokenizer.ShAtom at \"`\" (quoting=bd).",
1.13    ! rillig    596:                "WARN: fuzzing.mk:4: Pkglint ShellLine.CheckShellCommand: splitIntoShellTokens couldn't parse \"`\\\"`\"",
1.8       rillig    597:
1.12      rillig    598:                "WARN: fuzzing.mk:5: Internal pkglint error in ShTokenizer.ShAtom at \"$`\" (quoting=bs).",
1.13    ! rillig    599:                "WARN: fuzzing.mk:5: Pkglint ShellLine.CheckShellCommand: splitIntoShellTokens couldn't parse \"`'$`\"",
1.12      rillig    600:                "WARN: fuzzing.mk:5: Internal pkglint error in MkLine.Tokenize at \"$`\".",
1.8       rillig    601:
1.13    ! rillig    602:                "WARN: fuzzing.mk:6: Pkglint ShellLine.CheckShellCommand: splitIntoShellTokens couldn't parse \"\\\"`'`y\"",
1.8       rillig    603:
1.12      rillig    604:                "WARN: fuzzing.mk:7: Internal pkglint error in ShTokenizer.ShAtom at \"$|\" (quoting=db).",
1.13    ! rillig    605:                "WARN: fuzzing.mk:7: Pkglint ShellLine.CheckShellCommand: splitIntoShellTokens couldn't parse \"\\\"`$|\"",
1.12      rillig    606:                "WARN: fuzzing.mk:7: Internal pkglint error in MkLine.Tokenize at \"$|\".",
1.8       rillig    607:
1.12      rillig    608:                "WARN: fuzzing.mk:8: Internal pkglint error in ShTokenizer.ShAtom at \"`\" (quoting=dbd).",
1.13    ! rillig    609:                "WARN: fuzzing.mk:8: Pkglint ShellLine.CheckShellCommand: splitIntoShellTokens couldn't parse \"\\\"`\\\"`\"",
1.8       rillig    610:
                    611:                "WARN: fuzzing.mk:9: Invoking subshells via $(...) is not portable enough.",
                    612:
1.12      rillig    613:                "WARN: fuzzing.mk:10: Internal pkglint error in ShTokenizer.ShAtom at \"`\" (quoting=S).",
1.8       rillig    614:                "WARN: fuzzing.mk:10: Invoking subshells via $(...) is not portable enough.",
                    615:
1.12      rillig    616:                "WARN: fuzzing.mk:11: Internal pkglint error in ShTokenizer.ShAtom at \"$)\" (quoting=Ss).",
1.8       rillig    617:                "WARN: fuzzing.mk:11: Invoking subshells via $(...) is not portable enough.",
1.12      rillig    618:                "WARN: fuzzing.mk:11: Internal pkglint error in MkLine.Tokenize at \"$)\".",
1.8       rillig    619:
1.13    ! rillig    620:                "WARN: fuzzing.mk:12: Pkglint ShellLine.CheckShellCommand: splitIntoShellTokens couldn't parse \"\\\"`# comment\"")
1.8       rillig    621: }
                    622:
1.13    ! rillig    623: // In order to get 100% code coverage for the shell tokenizer, a panic() statement has been
        !           624: // added to each uncovered basic block. After that, this fuzzer quickly found relatively
        !           625: // small example programs that led to the uncovered code.
        !           626: //
        !           627: // This test is not useful as-is.
1.8       rillig    628: func (s *Suite) Test_ShTokenizer__fuzzing(c *check.C) {
                    629:        t := s.Init(c)
                    630:
                    631:        fuzzer := NewFuzzer()
                    632:        fuzzer.Char("\"'`$();|_#", 10)
                    633:        fuzzer.Range('a', 'z', 5)
                    634:
                    635:        defer fuzzer.CheckOk()
                    636:        for i := 0; i < 1000; i++ {
                    637:                tokenizer := NewShTokenizer(dummyLine, fuzzer.Generate(50), false)
                    638:                tokenizer.ShAtoms()
1.13    ! rillig    639:                t.Output() // Discard the output, only react on panics.
1.1       rillig    640:        }
1.8       rillig    641:        fuzzer.Ok()
1.1       rillig    642: }

CVSweb <webmaster@jp.NetBSD.org>