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
46 changes: 46 additions & 0 deletions .github/workflows/go-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Go CI

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
name: Run go test
runs-on: ubuntu-latest
env:
# TestPatcher:
# absolute path in localsession.json
# TestCases:
# hardcoded obselete json
# Test_goParser_ParseNode:
# test tries to retrieve non-existent node
# TestParser_NodeFieldsConsistency:
# Vars.Content only includes name, not whole body
SKIPPED_TESTS: >-
TestPatcher|TestCases|Test_goParser_ParseNode|TestParser_NodeFieldsConsistency

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rust-analyzer

- name: Install gopls
run: go install golang.org/x/tools/gopls@latest

- name: Run all tests
run: go test ./lang/... -skip '${{ env.SKIPPED_TESTS }}'
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ __pycache__
rust-analyzer-x86_64-unknown-linux-gnu

testdata/test
testdata/repos
testdata/jsons

src/lang/testdata
Expand All @@ -77,4 +78,4 @@ src/lang/testdata
tools
abcoder

!testdata/asts/*.json
!testdata/asts/*.json
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ see [UniAST Specification](docs/uniast-zh.md)
abcoder parse go localsession -o /abcoder-asts/localsession.json
```

To parse repositories in other languages, [install the corresponding langauge server first](./docs/lsp-installation-en.md).
To parse repositories in other languages, [install the corresponding language server first](./docs/lsp-installation-en.md).

3. Integrate ABCoder's MCP tools into your AI agent.

Expand Down Expand Up @@ -80,7 +80,7 @@ see [UniAST Specification](docs/uniast-zh.md)

- You can add more repo ASTs into the AST directory without restarting abcoder MCP server.

- Try to use [the recommaned prompt](llm/prompt/analyzer.md) and combine planning/memory tools like [sequential-thinking](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking) in your AI agent.
- Try to use [the recommened prompt](llm/prompt/analyzer.md) and combine planning/memory tools like [sequential-thinking](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking) in your AI agent.


## Use ABCoder as an Agent (WIP)
Expand Down
File renamed without changes.
109 changes: 28 additions & 81 deletions lang/collect/collect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,103 +17,50 @@ package collect
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
"time"

"github.com/cloudwego/abcoder/lang/log"
"github.com/cloudwego/abcoder/lang/lsp"
"github.com/cloudwego/abcoder/lang/testutils"
"github.com/cloudwego/abcoder/lang/uniast"
)

var testroot = "../../../testdata"

func TestCollector_Collect(t *testing.T) {
root := testroot + "/rust2"

root, _ = filepath.Abs(root)
log.SetLogLevel(log.DebugLevel)
rustLSP, err := lsp.NewLSPClient(root, root+"/src/main.rs", time.Second*5, lsp.ClientOptions{
Server: "rust-analyzer",
Language: "rust",
Verbose: true,
})
rustLSP, rustTestCase, err := lsp.InitLSPForFirstTest(uniast.Rust, "rust-analyzer")
if err != nil {
fmt.Printf("Failed to initialize rust LSP client: %v", err)
t.Fatalf("Failed to initialize rust LSP client: %v", err)
}
defer rustLSP.Close()

tests := []struct {
name string
want *uniast.Repository
wantErr bool
}{
{
name: "rust",
want: &uniast.Repository{},
wantErr: false,
},
}
dir := testroot
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := NewCollector(root, rustLSP)
c.LoadExternalSymbol = true
err := c.Collect(context.Background())
if (err != nil) != tt.wantErr {
t.Errorf("Collector.Collect() error = %v, wantErr %v", err, tt.wantErr)
return
}
js1, err := json.Marshal(c.syms)
if err != nil {
t.Fatalf("Marshal symbols failed: %v", err)
}
if err := os.WriteFile(dir+"/symbols.json", js1, 0644); err != nil {
t.Fatalf("Write json failed: %v", err)
}
// if !reflect.DeepEqual(got, tt.want) {
// t.Errorf("Collector.Collect() = %#v, want %#v", got, tt.want)
// }
// for sym, content := range c.symbols {
// if sym.Name == "add" {
// t.Logf("symbol: %#v, content:%s", sym, content)
// }
// }
js3, err := json.Marshal(c.deps)
if err != nil {
t.Fatalf("Marshal deps failed: %v", err)
}
if err := os.WriteFile(dir+"/deps.json", js3, 0644); err != nil {
t.Fatalf("Write json failed: %v", err)
}
js4, err := json.Marshal(c.funcs)
if err != nil {
t.Fatalf("Marshal methods failed: %v", err)
}
if err := os.WriteFile(dir+"/funcs.json", js4, 0644); err != nil {
t.Fatalf("Write json failed: %v", err)
}
js5, err := json.Marshal(c.vars)
if err != nil {
t.Fatalf("Marshal methods failed: %v", err)
}
if err := os.WriteFile(dir+"/vars.json", js5, 0644); err != nil {
t.Fatalf("Write json failed: %v", err)
}
t.Run("rustCollect", func(t *testing.T) {
c := NewCollector(rustTestCase, rustLSP)
c.LoadExternalSymbol = true
err := c.Collect(context.Background())
if err != nil {
t.Fatalf("Collector.Collect() failed = %v\n", err)
}

repo, err := c.Export(context.Background())
if err != nil {
t.Fatalf("export repo failed: %v", err)
}
js6, err := json.Marshal(repo)
outdir := testutils.MakeTmpTestdir(true)
marshals := []struct {
val any
name string
}{
{&c.syms, "symbols"},
{&c.deps, "deps"},
{&c.funcs, "funcs"},
{&c.vars, "vars"},
{&c.repo, "repo"},
}
for _, m := range marshals {
js, err := json.Marshal(m.val)
if err != nil {
t.Fatalf("Marshal methods failed: %v", err)
t.Fatalf("Marshal %s failed: %v", m.name, err)
}
if err := os.WriteFile(dir+"/repo.json", js6, 0644); err != nil {
t.Fatalf("Write json failed: %v", err)
if err := os.WriteFile(outdir+"/"+m.name+".json", js, 0644); err != nil {
t.Fatalf("Write json %s failed: %v", m.name, err)
}
})
}
}
})
}
46 changes: 21 additions & 25 deletions lang/golang/parser/pkg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,23 @@ package parser

import (
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"testing"

"github.com/cloudwego/abcoder/lang/testutils"
. "github.com/cloudwego/abcoder/lang/uniast"
)

const localSessURL = "github.com/cloudwego/localsession"

func Test_goParser_ParseRepo(t *testing.T) {
type fields struct {
modName string
homePageDir string
id Identity
}
tests := []struct {
name string
Expand All @@ -39,40 +41,31 @@ func Test_goParser_ParseRepo(t *testing.T) {
{
name: "test",
fields: fields{
modName: "github.com/cloudwego/localsession",
homePageDir: "../../../tmp/localsession",
modName: localSessURL,
homePageDir: "localsession",
id: NewIdentity(localSessURL, localSessURL+"/backup", "RecoverCtxOnDemands"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
abs, _ := filepath.Abs(tt.fields.homePageDir)
println(abs)
p := newGoParser(tt.fields.modName, tt.fields.homePageDir, Options{
repoDir, err := testutils.GitCloneFast(tt.fields.modName, tt.fields.homePageDir, "main")
if err != nil {
t.Fatalf("failed to clone repo %s", err)
}
p := newGoParser(tt.fields.modName, repoDir, Options{
ReferCodeDepth: 1,
NeedTest: true,
})
r, err := p.ParseRepo()
if err != nil {
t.Fatal(err)
t.Fatalf("failed to parse repo %s", err)
}
r.BuildGraph()
// spew.Dump(p)
pj, err := json.MarshalIndent(r, "", " ")
_, err = p.getNode(tt.fields.id)
if err != nil {
t.Fatal(err)
t.Fatalf("failed to get node %s", err)
}
_ = pj
_ = os.WriteFile("ast.json", pj, 0644)
n, err := p.getNode(NewIdentity("github.com/cloudwego/localsession", "github.com/cloudwego/localsession/backup", "RecoverCtxOnDemands"))
if err != nil {
t.Fatal(err)
}
jf, err := json.MarshalIndent(n, "", " ")
if err != nil {
t.Fatalf("json.Marshal() error = %v", err)
}
os.WriteFile("node.json", jf, 0644)
})
}
}
Expand All @@ -93,7 +86,7 @@ func Test_goParser_ParseDirs(t *testing.T) {
{
name: "test",
args: args{
homePageDir: "../../../testdata/golang",
homePageDir: testutils.FirstTest("go"),
modName: "a.b/c",
pkg: "a.b/c/cmd",
opts: Options{
Expand Down Expand Up @@ -137,7 +130,6 @@ type Struct struct {
t.Fatal(err)
}
ast.Inspect(node, func(n ast.Node) bool {
fmt.Printf("%#v\n", n)
if sel, ok := n.(*ast.SelectorExpr); ok {
println("selector:", string(GetRawContent(fset, []byte(src), sel, false)))
}
Expand All @@ -157,6 +149,10 @@ func Test_goParser_ParseNode(t *testing.T) {
pkgPath string
name string
}
localSessionDir, err := testutils.GitCloneFast(localSessURL, "localsession", "main")
if err != nil {
t.Fatalf("failed to clone repo %s", err)
}
tests := []struct {
name string
fields fields
Expand All @@ -167,7 +163,7 @@ func Test_goParser_ParseNode(t *testing.T) {
name: "test",
fields: fields{
modName: "github.com/cloudwego/localsession",
homePageDir: "../../../tmp/localsession",
homePageDir: localSessionDir,
},
args: args{
pkgPath: "github.com/modern-go/gls",
Expand Down
Loading