Skip to content
Merged
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
18 changes: 11 additions & 7 deletions .github/workflows/regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
#if: "!contains(github.event.pull_request.title, '[NO-REGRESSION-TEST]')"
env:
LANGS: "go rust python typescript cxx cpp"
LANGS: "go rust python typescript cxx cpp java"
# ignore package version for Go e.g. 'a.b/c@506fb8ece467f3a71c29322169bef9b0bc92d554'
DIFFJSON_IGNORE: >
['id']
Expand Down Expand Up @@ -82,14 +82,16 @@ jobs:
run: |
# HACK: auto installation uses the published version, not our local version
(cd ./main_repo/ts-parser && npm install && npm run build && npm install -g .)
OUTDIR=out_old ABCEXE=./abcoder_old ./pr_repo/script/run_testdata.sh first
JAVA_PARSER_JAR_PATH=$(realpath ./main_repo/lang/java/ipc/abcoder-java-analyzer-1.0-SNAPSHOT.jar) \
OUTDIR=out_old ABCEXE=./abcoder_old ./pr_repo/script/run_testdata.sh first
# avoid wasting time install a new jdtls
echo "JDTLS_ROOT_PATH=$(realpath ./main_repo/lang/java/lsp/jdtls/jdt-language-server-*)" >> $GITHUB_ENV

- name: Run OLD abcoder
run:
run: |
# we run the old abcoder on the new data to compare the outputs
OUTDIR=out_old ABCEXE=./abcoder_old ./pr_repo/script/run_testdata.sh all
JAVA_PARSER_JAR_PATH=$(realpath ./main_repo/lang/java/ipc/abcoder-java-analyzer-1.0-SNAPSHOT.jar) \
OUTDIR=out_old ABCEXE=./abcoder_old ./pr_repo/script/run_testdata.sh all

- name: Reset dependencies
run: |
Expand All @@ -98,11 +100,13 @@ jobs:
- name: Install dependencies for new
run: |
(cd ./pr_repo/ts-parser && npm install && npm run build && npm install -g .)
OUTDIR=out_new ABCEXE=./abcoder_new ./pr_repo/script/run_testdata.sh first
JAVA_PARSER_JAR_PATH=$(realpath ./pr_repo/lang/java/ipc/abcoder-java-analyzer-1.0-SNAPSHOT.jar) \
OUTDIR=out_new ABCEXE=./abcoder_new ./pr_repo/script/run_testdata.sh first

- name: Run NEW abcoder
run:
OUTDIR=out_new ABCEXE=./abcoder_new ./pr_repo/script/run_testdata.sh all
run: |
JAVA_PARSER_JAR_PATH=$(realpath ./pr_repo/lang/java/ipc/abcoder-java-analyzer-1.0-SNAPSHOT.jar) \
OUTDIR=out_new ABCEXE=./abcoder_new ./pr_repo/script/run_testdata.sh all

- name: Upload output directories
uses: actions/upload-artifact@v4
Expand Down
21 changes: 21 additions & 0 deletions lang/collect/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,27 @@ func (c *Collector) exportSymbol(repo *uniast.Repository, symbol *DocumentSymbol

tmp := uniast.NewIdentity(mod, path, name)
id = &tmp

// Eagerly prefix Identity.Name for methods so a cyclic visit
// (receiver Type -> receivers map -> back to this method via the
// visited cache) reads the final name, not the bare one. Without
// this, Type.Methods[k] = *mid value-copies a partially-built id
// non-deterministically. Cpp finalizes name in the SKMethod branch
// because it needs extractCppCallSig + namespace munging.
if c.Language != uniast.Cpp && (symbol.Kind == SKMethod || symbol.Kind == SKFunction) {
if mi := c.funcs[symbol].Method; mi != nil && mi.Receiver.Symbol != nil {
recvName := mi.Receiver.Symbol.Name
if mi.Interface != nil && mi.Interface.Symbol != nil {
recvName = mi.Interface.Symbol.Name + "<" + recvName + ">"
}
sep := "."
if symbol.Kind == SKFunction {
sep = "::"
}
id.Name = recvName + sep + name
}
}

// Save to visited ONLY WHEN no errors occur
visited[symbol] = id

Expand Down
30 changes: 30 additions & 0 deletions lang/uniast/ast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,36 @@ func TestRepository_BuildGraph(t *testing.T) {
}
}

// TestRepository_BuildGraph_Deterministic ensures BuildGraph yields a byte-stable
// JSON repeatedly. Relation slices (References, Dependencies, etc.) are filled
// via map iteration, so without an explicit canonical sort each run produced a
// different order and downstream diffs lit up spurious changes.
func TestRepository_BuildGraph_Deterministic(t *testing.T) {
astFile := testutils.GetTestAstFile("localsession")

var prev []byte
for i := 0; i < 5; i++ {
r, err := LoadRepo(astFile)
if err != nil {
t.Fatalf("iter %d: load repo: %v", i, err)
}
if err := r.BuildGraph(); err != nil {
t.Fatalf("iter %d: build graph: %v", i, err)
}
js, err := json.Marshal(r)
if err != nil {
t.Fatalf("iter %d: marshal: %v", i, err)
}
if i == 0 {
prev = js
continue
}
if string(prev) != string(js) {
t.Fatalf("iter %d: BuildGraph output is not byte-stable across runs (len %d vs %d)", i, len(prev), len(js))
}
}
}

func BenchmarkRepository_BuildGraph(b *testing.B) {
astFile := testutils.GetTestAstFile("large_ast")
r, err := LoadRepo(astFile)
Expand Down
26 changes: 26 additions & 0 deletions lang/uniast/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package uniast

import (
"sort"
"strconv"
"strings"
)
Expand Down Expand Up @@ -257,6 +258,31 @@ func (r *Repository) BuildGraph() error {
}
}
}

// Canonicalize relation slice order. AddRelation is fed from map
// iterations, so insertion order varies between runs.
sortRelations := func(rs []Relation) {
if len(rs) < 2 {
return
}
sort.Slice(rs, func(i, j int) bool {
a, b := rs[i].Identity.Full(), rs[j].Identity.Full()
if a != b {
return a < b
}
if rs[i].Line != rs[j].Line {
return rs[i].Line < rs[j].Line
}
return rs[i].Kind < rs[j].Kind
})
}
for _, node := range r.Graph {
sortRelations(node.Dependencies)
sortRelations(node.References)
sortRelations(node.Implements)
sortRelations(node.Inherits)
sortRelations(node.Groups)
}
return nil
}

Expand Down
Loading