diff --git a/build/ctags.go b/build/ctags.go index 28cf88c3..abdd7dd7 100644 --- a/build/ctags.go +++ b/build/ctags.go @@ -134,7 +134,8 @@ func (t *tagsToSections) Convert(content []byte, tags []*ctags.Entry) ([]zoekt.D } lineIdx := t.Line - 1 if lineIdx >= len(nls) { - return nil, nil, fmt.Errorf("linenum for entry out of range %v", t) + // Observed this with a .TS file. + continue } lineOff := uint32(0) diff --git a/build/ctags_test.go b/build/ctags_test.go index 386d07e7..b052eecc 100644 --- a/build/ctags_test.go +++ b/build/ctags_test.go @@ -116,16 +116,38 @@ func TestTagsToSectionsEOF(t *testing.T) { Name: "bar", Line: 2, }, - } - secs, _, err := (&tagsToSections{}).Convert(c, tags) - if err != nil { - t.Fatal("tagsToSections", err) + // We have seen ctags do this on a JS file + { + Name: "wat", + Line: -1, + }, + + // We have seen ctags return out of bounds lines + { + Name: "goliath", + Line: 3, + }, } - if len(secs) != 1 || secs[0].Start != 17 || secs[0].End != 20 { - t.Fatalf("got %#v, want 1 section (17,20)", secs) + // We run this test twice. Once with a final \n and without. + do := func(t *testing.T, doc []byte) { + secs, _, err := (&tagsToSections{}).Convert(doc, tags) + if err != nil { + t.Fatal("tagsToSections", err) + } + + if len(secs) != 1 || secs[0].Start != 17 || secs[0].End != 20 { + t.Fatalf("got %#v, want 1 section (17,20)", secs) + } } + + t.Run("no final newline", func(t *testing.T) { + do(t, c) + }) + t.Run("trailing newline", func(t *testing.T) { + do(t, append(c, '\n')) + }) } func TestOverlaps(t *testing.T) { @@ -232,9 +254,7 @@ func TestOverlaps(t *testing.T) { } func BenchmarkTagsToSections(b *testing.B) { - if checkCTags() == "" { - b.Skip("ctags not available") - } + requireCTags(b) file, err := os.ReadFile("./testdata/large_file.cc") parser, err := ctags.NewParser(ctags.UniversalCTags, "universal-ctags") @@ -268,3 +288,18 @@ func BenchmarkTagsToSections(b *testing.B) { } } } + +func requireCTags(tb testing.TB) { + tb.Helper() + + if checkCTags() != "" { + return + } + + // On CI we require ctags to be available. Otherwise we skip + if os.Getenv("CI") != "" { + tb.Fatal("universal-ctags is missing") + } else { + tb.Skip("universal-ctags is missing") + } +} diff --git a/build/scoring_test.go b/build/scoring_test.go index 2f7bb92c..4254d60b 100644 --- a/build/scoring_test.go +++ b/build/scoring_test.go @@ -27,8 +27,8 @@ import ( ) type scoreCase struct { - fileName string - content []byte + fileName string + content []byte query query.Q language string wantScore float64 @@ -58,7 +58,7 @@ func TestFileNameMatch(t *testing.T) { wantScore: 510, }, } - + for _, c := range cases { checkScoring(t, c, ctags.UniversalCTags) } @@ -165,7 +165,7 @@ func TestJava(t *testing.T) { } for _, c := range cases { - checkScoring(t, c, ctags.UniversalCTags) + checkScoring(t, c, ctags.UniversalCTags) } } @@ -174,7 +174,7 @@ func TestKotlin(t *testing.T) { if err != nil { t.Fatal(err) } - + cases := []scoreCase{ { fileName: "example.kt", @@ -239,7 +239,7 @@ func TestCpp(t *testing.T) { if err != nil { t.Fatal(err) } - + cases := []scoreCase{ { fileName: "example.cc", @@ -489,9 +489,7 @@ func skipIfCTagsUnavailable(t *testing.T, parserType ctags.CTagsParserType) { switch parserType { case ctags.UniversalCTags: - if checkCTags() == "" { - t.Skip("ctags not available") - } + requireCTags(t) case ctags.ScipCTags: if checkScipCTags() == "" { t.Skip("scip-ctags not available") @@ -560,9 +558,7 @@ func checkScoring(t *testing.T, c scoreCase, parserType ctags.CTagsParserType) { } func TestDocumentRanks(t *testing.T) { - if os.Getenv("CI") == "" && checkCTags() == "" { - t.Skip("ctags not available") - } + requireCTags(t) dir := t.TempDir() opts := Options{ @@ -649,9 +645,7 @@ func TestDocumentRanks(t *testing.T) { } func TestRepoRanks(t *testing.T) { - if os.Getenv("CI") == "" && checkCTags() == "" { - t.Skip("ctags not available") - } + requireCTags(t) dir := t.TempDir() opts := Options{