-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpython_import_paths_test.go
More file actions
132 lines (126 loc) · 4.97 KB
/
Copy pathpython_import_paths_test.go
File metadata and controls
132 lines (126 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package codegraph
import (
"context"
"errors"
"strings"
"testing"
)
// +case:id=python-package-layout,expect=`Absolute imports use bounded layout candidates and never claim runtime search-path certainty`
func TestPythonAbsoluteImportsBelowSnapshotRoot(t *testing.T) {
for _, prefix := range []string{"", "src/", "sdks/python/source/"} {
t.Run(prefix, func(t *testing.T) {
g, _, err := Build(context.Background(), "layout", []Document{
{Path: prefix + "pkg/app.py", Content: []byte("from pkg.lib import work as run\nimport pkg.lib as api\ndef entry():\n run()\n api.work()\ndef shadow(run):\n run()\n")},
{Path: prefix + "pkg/lib.py", Content: []byte("def work(): pass\n")},
}, Options{})
if err != nil {
t.Fatal(err)
}
rows := query(t, g, `MATCH (:Function {name:'entry'})-[r:calls]->(target:Function {name:'work'}) RETURN r,target`, nil)
if len(rows) != 2 {
t.Fatalf("calls=%v diagnostics=%v", rows, g.Report().Diagnostics)
}
for _, row := range rows {
if row["r"].(Relation).Confidence != Scoped || row["target"].(Node).Location.Path != prefix+"pkg/lib.py" {
t.Fatal(row)
}
}
if rows := query(t, g, `MATCH (:Function {name:'shadow'})-[:calls]->(:Function {name:'work'}) RETURN 1`, nil); len(rows) != 0 {
t.Fatal(rows)
}
refs := query(t, g, `MATCH (:Function {name:'entry'})-[r:references]->(:Function {name:'work'}) RETURN r`, nil)
if len(refs) != 2 {
t.Fatal(refs)
}
for _, row := range refs {
if row["r"].(Relation).Confidence != Scoped {
t.Fatal(row)
}
}
})
}
}
func TestPythonPackageLayoutReExportAndReload(t *testing.T) {
ctx := context.Background()
g, report, err := Build(ctx, "reload", []Document{{Path: "sdk/pkg/app.py", Content: []byte("from pkg import run\ndef entry(): return run()\n")}}, Options{})
if err != nil || !hasDiagnostic(report, "unresolved_import") {
t.Fatal(report, err)
}
if err := g.AddDocuments(ctx,
Document{Path: "sdk/pkg/__init__.py", Content: []byte("from .lib import work as run\n")},
Document{Path: "sdk/pkg/lib.py", Content: []byte("def work(): pass\n")},
); err != nil {
t.Fatal(err)
}
if _, err := g.Wait(ctx); err != nil {
t.Fatal(err)
}
rows := query(t, g, `MATCH (:Function {name:'entry'})-[r:calls]->(target:Function {name:'work'}) RETURN r,target`, nil)
if len(rows) != 1 || rows[0]["r"].(Relation).Confidence != Scoped || rows[0]["target"].(Node).Location.Path != "sdk/pkg/lib.py" {
t.Fatal(rows)
}
}
func TestPythonPackageLayoutAmbiguityAndBoundaries(t *testing.T) {
g, _, err := Build(context.Background(), "ambiguous", []Document{
{Path: "src/pkg/app.py", Content: []byte("from pkg.lib import work\nfrom external.lib import missing\ndef entry():\n work()\n missing()\n")},
{Path: "pkg/lib.py", Content: []byte("def work(): pass\n")},
{Path: "src/pkg/lib.py", Content: []byte("def work(): pass\n")},
{Path: "elsewhere/pkg/lib.py", Content: []byte("def work(): pass\n")},
{Path: "src/external/lib.py", Content: []byte("def missing(): pass\n")},
}, Options{})
if err != nil {
t.Fatal(err)
}
rows := query(t, g, `MATCH (:Function {name:'entry'})-[r:calls]->(target:Function {name:'work'}) RETURN r,target`, nil)
if len(rows) != 2 {
t.Fatal(rows)
}
for _, row := range rows {
if row["r"].(Relation).Confidence != Scoped || strings.HasPrefix(row["target"].(Node).Location.Path, "elsewhere/") {
t.Fatal(row)
}
}
if rows := query(t, g, `MATCH (:Function {name:'entry'})-[:calls]->(:Function {name:'missing'}) RETURN 1`, nil); len(rows) != 0 {
t.Fatal("unrelated source roots must not be guessed", rows)
}
}
func TestPythonPackageLayoutStubsAndRelativeImports(t *testing.T) {
for _, ext := range []string{".py", ".pyi"} {
t.Run(ext, func(t *testing.T) {
g, _, err := Build(context.Background(), "forms", []Document{
{Path: "sdk/pkg/app.py", Content: []byte("from .lib import work as relative\nfrom pkg.lib import work as absolute\ndef entry():\n relative()\n absolute()\n")},
{Path: "sdk/pkg/lib" + ext, Content: []byte("def work(): ...\n")},
}, Options{})
if err != nil {
t.Fatal(err)
}
rows := query(t, g, `MATCH (:Function {name:'entry'})-[r:calls]->(:Function {name:'work'}) RETURN r`, nil)
if len(rows) != 2 {
t.Fatal(rows)
}
counts := map[Confidence]int{}
for _, row := range rows {
counts[row["r"].(Relation).Confidence]++
}
if counts[Exact] != 1 || counts[Scoped] != 1 {
t.Fatal(counts)
}
})
}
}
func TestPythonPackageLayoutBudgetIsAtomic(t *testing.T) {
ctx := context.Background()
g, err := New("budget", Options{MaxRelations: 1})
if err != nil {
t.Fatal(err)
}
if err := g.AddDocuments(ctx,
Document{Path: "src/pkg/app.py", Content: []byte("from pkg.lib import work\ndef entry(): return work()\n")},
Document{Path: "src/pkg/lib.py", Content: []byte("def work(): pass\n")},
); err != nil {
t.Fatal(err)
}
if _, err := g.Wait(ctx); !errors.Is(err, ErrBuildBudget) || len(g.Nodes()) != 0 || len(g.Relations()) != 0 {
t.Fatal(err, g.Nodes(), g.Relations())
}
}