1package main23import (4 "fmt"5 "io/ioutil"6 "os"7 "sort"8 "strconv"9 "strings"10 "testing"11)1213const name = "Testinput"1415var reader *os.File16var writer *os.File1718type Msg struct {19 l uint20 c uint21 s string22}2324type byPos []string2526func (p byPos) Len() int {27 return len(p)28}2930func (p byPos) Swap(i, j int) {31 p[i], p[j] = p[j], p[i]32}3334func (p byPos) Less(i, j int) bool {35 linei := p[i]36 linej := p[j]3738 li, ci, _ := parseLine(linei)39 if li == 0 {40 return true41 }4243 lj, cj, _ := parseLine(linej)44 if li == lj {45 return ci < cj46 }4748 return li < lj49}5051func setup() {52 var err error53 reader, writer, err = os.Pipe()54 if err != nil {55 panic(err)56 }57}5859func parseLine(line string) (uint, uint, string) {60 sep := strings.Index(line, ":")61 if line[sep+1] == ' ' {62 return 0, 0, line[sep+2:]63 }64 index := sep + 16566 lineLen := strings.Index(line[index:], ":")67 lineInfo := line[index : index+lineLen]6869 l, err := strconv.ParseUint(lineInfo, 10, 16)70 if err != nil {71 panic(err)72 }73 index += lineLen + 17475 columLen := strings.Index(line[index:], ":")76 columnInfo := line[index : index+columLen]7778 c, err := strconv.ParseUint(columnInfo, 10, 16)79 if err != nil {80 panic(err)81 }8283 index += columLen + 284 return uint(l), uint(c), line[index:]85}8687func newLinter(input string) *Linter {88 reader := strings.NewReader(input)89 abuild, err := Parse(reader, name)90 if err != nil {91 panic(err)92 }9394 linter := Linter{f: abuild, w: writer}95 return &linter96}9798func expMsg(t *testing.T, msgs ...Msg) {99 writer.Close() // Write EOF100 defer setup()101102 data, err := ioutil.ReadAll(reader)103 if err != nil {104 t.Fatal("ioutil.ReadAll failed:", err)105 }106107 str := string(data)108 lines := strings.Split(str[0:len(str)-1], "\n")109 if len(msgs) != len(lines) {110 t.Fatalf("Expected %d violations, got %d",111 len(msgs), len(lines))112 }113114 // Output of some linter functions is non-deterministic,115 // e.g. lintLocalVariables → sort output by token position.116 sort.Sort(byPos(lines))117118 for n, m := range msgs {119 line, column, text := parseLine(lines[n])120 if line != m.l {121 t.Fatalf("expFail: Line didn't match, expected %d - got %d", m.l, line)122 }123 if column != m.c {124 t.Fatalf("expFail: Column didn't match, expected %d - got %d", m.c, column)125 }126 if text != m.s {127 t.Fatalf("expFail Expected string %q - got %q", m.s, text)128 }129 }130}131132func TestLintComments(t *testing.T) {133 input := `#barfoo134#135# foobar136# bazbar137#foobaz`138139 l := newLinter(input)140 l.lintComments()141142 expMsg(t,143 Msg{1, 1, badCommentPrefix},144 Msg{4, 1, badCommentPrefix},145 Msg{5, 1, badCommentPrefix})146}147148func TestLintAddressComments(t *testing.T) {149 input := `# foo: foo bar <foo@bar.com>150# faz:151# foo:152# foo:153# foo:foo bar <foo@bar.com>154# foo: …`155156 l := newLinter(input)157 n, addrs := l.lintAddressComments(" foo:")158 if n != 5 || len(addrs) != 1 || !l.v {159 t.Fail()160 }161162 if addrs[0].a.String() != "\"foo bar\" <foo@bar.com>" {163 t.Fatalf("Expected %q - got %q", "foo",164 addrs[0].a.String())165 }166167 expMsg(t,168 Msg{3, 1, missingAddress},169 Msg{4, 1, missingAddress},170 Msg{5, 1, noAddressSeparator},171 Msg{6, 1, invalidAddress})172}173174func TestLintMaintainerAndContributors(t *testing.T) {175 t.Run("missingMaintainer", func(t *testing.T) {176 l := newLinter("")177 l.lintMaintainerAndContributors()178 expMsg(t, Msg{0, 0, missingMaintainer})179 })180181 t.Run("emptyMaintainer", func(t *testing.T) {182 l := newLinter("# Maintainer:")183 l.lintMaintainerAndContributors()184 expMsg(t, Msg{1, 1, missingAddress})185 })186187 t.Run("tooManyMaintainers", func(t *testing.T) {188 l := newLinter(`# Maintainer: A <a@a>189# Maintainer: B <b@b>`)190 l.lintMaintainerAndContributors()191 expMsg(t, Msg{2, 1, tooManyMaintainers})192 })193194 t.Run("maintainerAfterAssign", func(t *testing.T) {195 l := newLinter(`pkgname=foo196# Maintainer: A <a@b>`)197 l.lintMaintainerAndContributors()198 expMsg(t, Msg{2, 1, maintainerAfterAssign})199 })200201 t.Run("wrongAddrCommentOrder", func(t *testing.T) {202 l := newLinter(`# Maintainer: A <a@b>203# Contributor: B <b@c>`)204 l.lintMaintainerAndContributors()205 expMsg(t, Msg{2, 1, wrongAddrCommentOrder})206 })207208 t.Run("repeatedAddrComment", func(t *testing.T) {209 l := newLinter(`# Contributor: A <a@b>210# Contributor: A <a@b>211# Maintainer: M <m@m>`)212 l.lintMaintainerAndContributors()213 expMsg(t, Msg{2, 1, repeatedAddrComment})214 })215216 t.Run("oneMaintainer", func(t *testing.T) {217 l := newLinter("# Maintainer: J <a@k>")218 l.lintMaintainerAndContributors()219 if l.v {220 t.Fail()221 }222 })223224 t.Run("oneMaintainerAndContributors", func(t *testing.T) {225 l := newLinter(`# Contributor: A <a@a>226# Contributor: B <b@b>227# Maintainer: C <c@c>`)228 l.lintMaintainerAndContributors()229 if l.v {230 t.Fail()231 }232 })233}234235func TestListGlobalVariables(t *testing.T) {236 input := `pkgname=foobar237foo=42238_foo=9001239__foo=bar240export ENV=23`241242 l := newLinter(input)243 l.lintGlobalVariables()244245 expMsg(t,246 Msg{2, 1, fmt.Sprintf(invalidGlobalVar, "foo")},247 Msg{4, 1, fmt.Sprintf(invalidGlobalVar, "__foo")})248}249250func TestLintUnusedVariables(t *testing.T) {251 input := `pkgname=foobar252_foo=23253_bar=42254f1() {255foo=lol256}257f2() {258echo $_bar259}260f3() {261FOO=bar make262}263f4() {264export ENV=42265}`266267 l := newLinter(input)268 l.lintUnusedVariables()269270 expMsg(t,271 Msg{2, 1, fmt.Sprintf(variableUnused, "_foo")},272 Msg{5, 1, fmt.Sprintf(variableUnused, "foo")})273}274275func TestLintGlobalCmdSubsts(t *testing.T) {276 input := `pkgname=bar277_bar=$(ls)278f1() {279local v1=${_bar}280}281_baz=$(cp -h)282f2() {283local v2=${_baz}284}285_baz=${foo} bar`286287 l := newLinter(input)288 l.lintGlobalCmdSubsts()289290 expMsg(t,291 Msg{2, 6, cmdSubstInGlobalVar},292 Msg{6, 6, cmdSubstInGlobalVar})293}294295func TestLintLocalVariables(t *testing.T) {296 input := `f1() {297foo=123298}299f2() {300local foo=123301}302f3() {303local bar=456304}305f4() {306for foobar in "a" "b" "c"; do echo "$foobar"; done307}308f5() {309export foo="bar"; echo "$foo"310}311VARFORCALLEXPR=23 ls`312313 l := newLinter(input)314 l.lintLocalVariables()315316 expMsg(t,317 Msg{2, 1, fmt.Sprintf(nonLocalVariable, "foo")},318 Msg{11, 5, fmt.Sprintf(nonLocalVariable, "foobar")})319}320321func TestLintParamExpression(t *testing.T) {322 input := `# foobar323foo=${pkgname}324bar=$foo325# barfoo326foo=${pkgname##.*}327foo=${foobar}foobar328foo=${foobar}.$barfoo`329330 l := newLinter(input)331 l.lintParamExpression()332333 expMsg(t,334 Msg{2, 5, fmt.Sprintf(trivialLongParamExp, "pkgname", "pkgname")},335 Msg{7, 5, fmt.Sprintf(trivialLongParamExp, "foobar", "foobar")})336}337338func TestVariablePlacement(t *testing.T) {339 input := `sha512sums=foobar340myfunc() {341echo myfunc342}343pkgname=barfoo`344345 l := newLinter(input)346 l.lintMetadataPlacement()347348 expMsg(t,349 Msg{1, 1, fmt.Sprintf(metadataAfterFunc, "sha512sums")},350 Msg{5, 1, fmt.Sprintf(metadataBeforeFunc, "pkgname")})351}352353func TestLintRequiredMetadata(t *testing.T) {354 t.Run("allVarsDefined", func(t *testing.T) {355 input := `pkgname=foobar356pkgver=1337357pkgrel=2342358pkgdesc="foobar"359url=http://example.org360arch=all361license=MIT362sha512sums=1234`363364 l := newLinter(input)365 l.lintRequiredMetadata()366 if l.v {367 t.Fail()368 }369 })370371 t.Run("missingOneVar", func(t *testing.T) {372 input := `pkgname=foobar373pkgrel=2342374pkgdesc="foobar"375url=http://example.org376arch=all377license=MIT378sha512sums=1234`379380 l := newLinter(input)381 l.lintRequiredMetadata()382383 expMsg(t,384 Msg{0, 0, fmt.Sprintf(missingMetadata, "pkgver")})385 })386}387388func TestLintFunctionOrder(t *testing.T) {389 t.Run("wrongFuncOrder", func(t *testing.T) {390 input := `package() {391}392build() {393}`394395 l := newLinter(input)396 l.lintFunctionOrder()397398 expMsg(t,399 Msg{1, 1, fmt.Sprintf(wrongFuncOrder, "package", "build")})400 })401402 t.Run("rightFuncOrder", func(t *testing.T) {403 input := `prepare() {404}405build() {406}407check() {408}409package() {410}`411412 l := newLinter(input)413 l.lintFunctionOrder()414415 if l.v {416 t.Fail()417 }418 })419}420421func TestLintBashisms(t *testing.T) {422 input := `[[ -e "$builddir" ]] && foo=bar423bar=*(foo bar)424echo >(true)425let x426declare -A foobar427readonly x428typeset -r x429nameref foo430echo ${#foo}431select s in foo bar baz; do432 echo $s433done434function f() {435return 1436}`437438 l := newLinter(input)439 l.lintBashisms()440441 expMsg(t,442 Msg{1, 1, fmt.Sprintf(forbiddenBashism, "test clause")},443 Msg{2, 5, fmt.Sprintf(forbiddenBashism, "extended globbing expression")},444 Msg{3, 6, fmt.Sprintf(forbiddenBashism, "process substitution")},445 Msg{4, 1, fmt.Sprintf(forbiddenBashism, "let clause")},446 Msg{5, 1, fmt.Sprintf(forbiddenBashism, "declare")},447 Msg{6, 1, fmt.Sprintf(forbiddenBashism, "readonly")},448 Msg{7, 1, fmt.Sprintf(forbiddenBashism, "typeset")},449 Msg{8, 1, fmt.Sprintf(forbiddenBashism, "nameref")},450 Msg{9, 6, fmt.Sprintf(forbiddenBashism, "advanced parameter expression")},451 Msg{10, 1, fmt.Sprintf(forbiddenBashism, "select clause")},452 Msg{13, 1, fmt.Sprintf(forbiddenBashism, "non-POSIX function declaration")})453}454455func TestMain(m *testing.M) {456 setup()457 os.Exit(m.Run())458}