Skip to content
This repository was archived by the owner on Mar 29, 2021. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions cmd/lsif-test/args.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"os"

"github.com/alecthomas/kingpin"
)

var app = kingpin.New(
"lsif-test",
"lsif-test is test runner for validating LSIF indexer output.",
).Version(version)

var (
indexFile *os.File
testsFile *os.File
)

func init() {
app.HelpFlag.Short('h')
app.VersionFlag.Short('v')
app.HelpFlag.Hidden()

app.Arg("index-file", "The LSIF index to visualize.").Default("dump.lsif").FileVar(&indexFile)
app.Arg("tests-file", "The test specification file.").Default("tests.yaml").FileVar(&testsFile)
}

func parseArgs(args []string) (err error) {
if _, err := app.Parse(args); err != nil {
return err
}

return nil
}
15 changes: 15 additions & 0 deletions cmd/lsif-test/internal/runner/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package runner

import (
"github.com/sourcegraph/lsif-test/internal/reader"
)

type RunnerContext struct {
Stasher *reader.Stasher
}

func NewRunnerContext() *RunnerContext {
return &RunnerContext{
Stasher: reader.NewStasher(),
}
}
57 changes: 57 additions & 0 deletions cmd/lsif-test/internal/runner/runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package runner

import (
"fmt"
"io"
"os"

reader "github.com/sourcegraph/lsif-protocol/reader"
"github.com/sourcegraph/lsif-test/cmd/lsif-test/internal/search"
reader2 "github.com/sourcegraph/lsif-test/internal/reader"
)

type Runner struct {
Context *RunnerContext
}

func (v *Runner) Run(indexFile, testsFile io.Reader) error {
testSpecs, err := ReadTestSpecs(testsFile)
if err != nil {
return err
}

if err := reader2.Read(indexFile, v.Context.Stasher, nil, nil); err != nil {
return err
}

var vertices []reader.Element
_ = v.Context.Stasher.Vertices(func(lineContext reader2.LineContext) bool {
vertices = append(vertices, lineContext.Element)
return true
})

var edges []reader.Element
_ = v.Context.Stasher.Edges(func(lineContext reader2.LineContext, edge reader.Edge) bool {
edges = append(edges, lineContext.Element)
return true
})

for _, testSpec := range testSpecs {
rangeData := search.GatherRangeDataFromPosition(vertices, edges, testSpec.Path, testSpec.Line, testSpec.Character)
if len(rangeData) != 1 {
fmt.Printf("error: no or overlapping range data\n")
os.Exit(1)
}

if rangeData[0].HoverText != testSpec.HoverText {
fmt.Printf("error: bad hover text\n")
os.Exit(1)
}

// TODO - test definitions
// TODO - test references
// TODO - test monikers
}

return nil
}
39 changes: 39 additions & 0 deletions cmd/lsif-test/internal/runner/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package runner

import (
"io"
"io/ioutil"

"gopkg.in/yaml.v2"
)

type TestSpec struct {
Path string `yaml:"path"`
Line int `yaml:"line"`
Character int `yaml:"character"`
HoverText string `yaml:"hoverText"`
Definitions []Location `yaml:"definitions"`
References []Location `yaml:"references"`
}

type Location struct {
Path string `yaml:"path"`
StartLine int `yaml:"startLine"`
StartCharacter int `yaml:"startCharacter"`
EndLine int `yaml:"endLine"`
EndCharacter int `yaml:"endCharacter"`
}

func ReadTestSpecs(r io.Reader) ([]TestSpec, error) {
content, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}

var testSpecs []TestSpec
if err := yaml.Unmarshal(content, &testSpecs); err != nil {
return nil, err
}

return testSpecs, nil
}
241 changes: 241 additions & 0 deletions cmd/lsif-test/internal/search/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package search

import (
"strings"

"github.com/sourcegraph/lsif-protocol/reader"
)

type RangeData struct {
HoverText string
Definitions []Location
References []Location
}

type Location struct {
Path string
StartLine int
StartCharacter int
EndLine int
EndCharacter int
}

func GatherRangeDataFromPosition(vertices, edges []reader.Element, path string, line, character int) []RangeData {
var rangeData []RangeData
for _, rangeID := range findRangeByPosition(vertices, edges, path, line, character) {
rangeData = append(rangeData, gatherRangeDataFromID(vertices, edges, rangeID))
}

return rangeData
}

func gatherRangeDataFromID(vertices, edges []reader.Element, rangeID int) RangeData {
return RangeData{
HoverText: findHoverTextByRangeID(vertices, edges, rangeID),
Definitions: findDefinitionLocationsByRangeID(vertices, edges, rangeID),
References: findReferenceLocationsByRangeID(vertices, edges, rangeID),
}
}

func findRangeByPosition(vertices, edges []reader.Element, path string, line, character int) []int {
projectRoot := getProjectRoot(vertices, edges)

for _, vertex := range vertices {
if vertex.Label == "document" && strings.TrimPrefix(vertex.Payload.(string), projectRoot) == path {
return findOverlappingRanges(vertices, edges, findRangesContainedByDocument(vertices, edges, vertex.ID), line, character)
}
}

return nil
}

func getProjectRoot(vertices, edges []reader.Element) string {
for _, vertex := range vertices {
if vertex.Label == "metaData" {
projectRoot := vertex.Payload.(reader.MetaData).ProjectRoot
if !strings.HasSuffix(projectRoot, "/") {
projectRoot += "/"
}

return projectRoot
}
}

return ""
}

func findRangesContainedByDocument(vertices, edges []reader.Element, documentID int) []int {
for _, edge := range edges {
if payload := edge.Payload.(reader.Edge); edge.Label == "contains" && payload.OutV == documentID {
return payload.InVs
}
}

return nil
}

func findOverlappingRanges(vertices, edges []reader.Element, rangeIDs []int, line, character int) []int {
rangeIDMap := map[int]struct{}{}
for _, rangeID := range rangeIDs {
rangeIDMap[rangeID] = struct{}{}
}

var intersectingRangeIDs []int
for _, vertex := range vertices {
if _, ok := rangeIDMap[vertex.ID]; ok && contains(vertex.Payload.(reader.Range), line, character) {
intersectingRangeIDs = append(intersectingRangeIDs, vertex.ID)
}
}

return intersectingRangeIDs
}

func contains(r reader.Range, line, character int) bool {
if r.StartLine > line || r.EndLine < line {
return false
}
if r.StartLine == line && r.StartCharacter > character {
return false
}
if r.EndLine == line && r.EndCharacter < character {
return false
}

return true
}

func findHoverTextByRangeID(vertices, edges []reader.Element, rangeID int) string {
for _, edge := range edges {
if payload := edge.Payload.(reader.Edge); edge.Label == "textDocument/hover" && payload.OutV == rangeID {
return findHoverTextByID(vertices, edges, payload.InV)
}
}

for _, edge := range edges {
if payload := edge.Payload.(reader.Edge); edge.Label == "next" && payload.OutV == rangeID {
if hoverText := findHoverTextByRangeID(vertices, edges, payload.InV); hoverText != "" {
return hoverText
}
}
}

return ""
}

func findHoverTextByID(vertices, edges []reader.Element, hoverResultID int) string {
for _, vertex := range vertices {
if vertex.Label == "hoverResult" && vertex.ID == hoverResultID {
return vertex.Payload.(string)
}
}

return ""
}

//
//
// TODO
//
//

func findDefinitionLocationsByRangeID(vertices, edges []reader.Element, rangeID int) []Location {
for _, edge := range edges {
if payload := edge.Payload.(reader.Edge); edge.Label == "textDocument/definition" && payload.OutV == rangeID {
return findDefinitionLocationsByID(vertices, edges, payload.InV)
}
}

for _, edge := range edges {
if payload := edge.Payload.(reader.Edge); edge.Label == "next" && payload.OutV == rangeID {
if locations := findDefinitionLocationsByRangeID(vertices, edges, payload.InV); len(locations) > 0 {
return locations
}
}
}

return nil
}

func findDefinitionLocationsByID(vertices, edges []reader.Element, definitionResultID int) []Location {
var locations []Location
for _, edge := range edges {
if payload := edge.Payload.(reader.Edge); edge.Label == "item" && payload.OutV == definitionResultID {
for _, inV := range payload.InVs {
locations = append(locations, findLocationByID(vertices, edges, inV))
}
}
}

return locations
}

func findReferenceLocationsByRangeID(vertices, edges []reader.Element, rangeID int) []Location {
for _, edge := range edges {
if payload := edge.Payload.(reader.Edge); edge.Label == "textDocument/references" && payload.OutV == rangeID {
return findReferenceLocationsByID(vertices, edges, payload.InV)
}
}

for _, edge := range edges {
if payload := edge.Payload.(reader.Edge); edge.Label == "next" && payload.OutV == rangeID {
if locations := findReferenceLocationsByRangeID(vertices, edges, payload.InV); len(locations) > 0 {
return locations
}
}
}

return nil
}

func findReferenceLocationsByID(vertices, edges []reader.Element, referenceResultID int) []Location {
var locations []Location
for _, edge := range edges {
if payload := edge.Payload.(reader.Edge); edge.Label == "item" && payload.OutV == referenceResultID {
for _, inV := range payload.InVs {
locations = append(locations, findLocationByID(vertices, edges, inV))
}
}
}

return locations
}

func findLocationByID(vertices, edges []reader.Element, rangeID int) Location {
for _, vertex := range vertices {
if vertex.Label == "range" && vertex.ID == rangeID {
r := vertex.Payload.(reader.Range)

return Location{
Path: findURIContaining(vertices, edges, rangeID),
StartLine: r.StartLine,
StartCharacter: r.StartCharacter,
EndLine: r.EndLine,
EndCharacter: r.EndCharacter,
}
}
}

return Location{}
}

func findURIContaining(vertices, edges []reader.Element, rangeID int) string {
for _, edge := range edges {
if payload := edge.Payload.(reader.Edge); edge.Label == "contains" {
for _, inV := range payload.InVs {
if inV == rangeID {
projectRoot := getProjectRoot(vertices, edges)

for _, vertex := range vertices {
if vertex.Label == "document" && vertex.ID == payload.OutV {
return strings.TrimPrefix(vertex.Payload.(string), projectRoot)
}
}

return ""
}
}
}
}

return ""
}
Loading