From 080877faef7e0f0846c56d73fd69d3ae430e7142 Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Tue, 15 Sep 2026 16:32:40 -0400 Subject: [PATCH] feat: add context support to artifact writing and compilation functions --- cmd/internal/build/build.go | 7 +- cmd/internal/build/build_test.go | 38 ++++++++++- cmd/internal/check/check.go | 3 +- cmd/internal/check/check_test.go | 32 +++++++++ cmd/internal/debug/debug_test.go | 2 +- cmd/internal/inspect/inspect.go | 3 +- cmd/internal/inspect/inspect_test.go | 43 ++++++++++++ cmd/internal/run/run_test.go | 4 +- go.mod | 31 ++++----- go.sum | 62 +++++++++--------- pkg/build/artifact.go | 8 ++- pkg/build/build_test.go | 98 ++++++++++++++++++++++++---- pkg/debugger/command.go | 10 +-- pkg/debugger/command_test.go | 22 +++---- pkg/debugger/prompt.go | 12 ++-- pkg/debugger/prompt_test.go | 8 +-- pkg/debugger/render.go | 8 +-- pkg/debugger/render_test.go | 4 +- pkg/debugger/session.go | 8 ++- pkg/run/execute.go | 6 +- pkg/run/run_test.go | 2 +- pkg/runtime/debug_test.go | 4 +- 22 files changed, 303 insertions(+), 112 deletions(-) create mode 100644 cmd/internal/check/check_test.go create mode 100644 cmd/internal/inspect/inspect_test.go diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go index 4ac56cf..a1b9974 100644 --- a/cmd/internal/build/build.go +++ b/cmd/internal/build/build.go @@ -1,6 +1,7 @@ package build import ( + "context" "fmt" "os" @@ -29,7 +30,7 @@ func New(store *config.Store) *cobra.Command { return err } - return runBuild(args, output) + return runBuild(cmd.Context(), args, output) }, } @@ -38,7 +39,7 @@ func New(store *config.Store) *cobra.Command { return cmd } -func runBuild(args []string, output string) error { +func runBuild(ctx context.Context, args []string, output string) error { plan, err := clibuild.PlanOutputs(args, output) if err != nil { @@ -65,7 +66,7 @@ func runBuild(args []string, output string) error { failed := 0 for i, src := range sources { - if err := clibuild.WriteArtifact(c, src, plan.Targets[i].OutputPath); err != nil { + if err := clibuild.WriteArtifact(ctx, c, src, plan.Targets[i].OutputPath); err != nil { diagnostics.PrintError(err) failed++ } diff --git a/cmd/internal/build/build_test.go b/cmd/internal/build/build_test.go index 0ee82b4..9257fa8 100644 --- a/cmd/internal/build/build_test.go +++ b/cmd/internal/build/build_test.go @@ -1,6 +1,8 @@ package build import ( + "context" + "os" "path/filepath" "strings" "testing" @@ -8,6 +10,38 @@ import ( "github.com/MontFerret/cli/v2/cmd/internal/testutil" ) +func TestBuildCommandCanceledContext(t *testing.T) { + dir := t.TempDir() + inputs := []string{filepath.Join(dir, "first.fql"), filepath.Join(dir, "second.fql")} + + for _, input := range inputs { + testutil.WriteQuery(t, input, "RETURN 1") + } + + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + command := New(nil) + command.SetContext(ctx) + + stderr, err := testutil.CaptureStderr(t, func() error { + return command.RunE(command, inputs) + }) + if err == nil || err.Error() != "2 of 2 scripts failed to build" { + t.Fatalf("expected aggregated compilation failures, got %v", err) + } + + if strings.Count(stderr, context.Canceled.Error()) != len(inputs) { + t.Fatalf("expected cancellation diagnostics for both scripts, got %q", stderr) + } + + for _, name := range []string{"first.fqlc", "second.fqlc"} { + if _, err := os.Stat(filepath.Join(dir, name)); !os.IsNotExist(err) { + t.Fatalf("expected %s to be absent, got %v", name, err) + } + } +} + func TestRunBuild_MixedMultiFileBuildContinues(t *testing.T) { dir := t.TempDir() valid := filepath.Join(dir, "valid.fql") @@ -18,7 +52,7 @@ func TestRunBuild_MixedMultiFileBuildContinues(t *testing.T) { testutil.WriteQuery(t, invalid, "FOR item IN") _, err := testutil.CaptureStderr(t, func() error { - return runBuild([]string{valid, invalid}, outputDir) + return runBuild(t.Context(), []string{valid, invalid}, outputDir) }) if err == nil { @@ -41,7 +75,7 @@ func TestRunBuild_PlanErrorReturned(t *testing.T) { testutil.WriteQuery(t, output, "not a directory") _, err := testutil.CaptureStderr(t, func() error { - return runBuild([]string{inputA, inputB}, output) + return runBuild(t.Context(), []string{inputA, inputB}, output) }) if err == nil { diff --git a/cmd/internal/check/check.go b/cmd/internal/check/check.go index 5fe3f6c..1d8a7d9 100644 --- a/cmd/internal/check/check.go +++ b/cmd/internal/check/check.go @@ -39,8 +39,7 @@ func New(store *config.Store) *cobra.Command { failed := 0 for _, src := range sources { - _, err := c.Compile(src) - + _, err := c.Compile(cmd.Context(), src) if err != nil { diagnostics.PrintError(err) failed++ diff --git a/cmd/internal/check/check_test.go b/cmd/internal/check/check_test.go new file mode 100644 index 0000000..8d2d068 --- /dev/null +++ b/cmd/internal/check/check_test.go @@ -0,0 +1,32 @@ +package check + +import ( + "context" + "path/filepath" + "strings" + "testing" + + "github.com/MontFerret/cli/v2/cmd/internal/testutil" +) + +func TestCheckCommandCanceledContext(t *testing.T) { + input := filepath.Join(t.TempDir(), "query.fql") + testutil.WriteQuery(t, input, "RETURN 1") + + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + command := New(nil) + command.SetContext(ctx) + + stderr, err := testutil.CaptureStderr(t, func() error { + return command.RunE(command, []string{input}) + }) + if err == nil || err.Error() != "1 of 1 scripts have errors" { + t.Fatalf("expected compilation failure, got %v", err) + } + + if !strings.Contains(stderr, context.Canceled.Error()) { + t.Fatalf("expected cancellation diagnostic, got %q", stderr) + } +} diff --git a/cmd/internal/debug/debug_test.go b/cmd/internal/debug/debug_test.go index 55f9d66..87db83f 100644 --- a/cmd/internal/debug/debug_test.go +++ b/cmd/internal/debug/debug_test.go @@ -54,7 +54,7 @@ func TestExecuteDebugRejectsArtifact(t *testing.T) { sourcePath := filepath.Join(dir, "query.fql") artifactPath := filepath.Join(dir, "query.fqlc") testutil.WriteQuery(t, sourcePath, "RETURN 1") - if err := build.WriteArtifact(testutil.NewCompiler(t), source.New(sourcePath, "RETURN 1"), artifactPath); err != nil { + if err := build.WriteArtifact(t.Context(), testutil.NewCompiler(t), source.New(sourcePath, "RETURN 1"), artifactPath); err != nil { t.Fatal(err) } diff --git a/cmd/internal/inspect/inspect.go b/cmd/internal/inspect/inspect.go index a5a2a37..3821782 100644 --- a/cmd/internal/inspect/inspect.go +++ b/cmd/internal/inspect/inspect.go @@ -48,8 +48,7 @@ func New(store *config.Store) *cobra.Command { return fmt.Errorf("initialize compiler: %w", err) } - program, err := c.Compile(sources[0]) - + program, err := c.Compile(cmd.Context(), sources[0]) if err != nil { diagnostics.PrintError(err) return fmt.Errorf("compilation failed") diff --git a/cmd/internal/inspect/inspect_test.go b/cmd/internal/inspect/inspect_test.go new file mode 100644 index 0000000..73ca176 --- /dev/null +++ b/cmd/internal/inspect/inspect_test.go @@ -0,0 +1,43 @@ +package inspect + +import ( + "context" + "strings" + "testing" + + "github.com/MontFerret/cli/v2/cmd/internal/testutil" +) + +func TestInspectCommandCanceledContext(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + command := New(nil) + command.SetContext(ctx) + + if err := command.Flags().Set("eval", "RETURN 1"); err != nil { + t.Fatal(err) + } + + var stdout string + + stderr, err := testutil.CaptureStderr(t, func() error { + var runErr error + stdout, runErr = testutil.CaptureStdout(t, func() error { + return command.RunE(command, nil) + }) + + return runErr + }) + if err == nil || err.Error() != "compilation failed" { + t.Fatalf("expected compilation failure, got %v", err) + } + + if !strings.Contains(stderr, context.Canceled.Error()) { + t.Fatalf("expected cancellation diagnostic, got %q", stderr) + } + + if stdout != "" { + t.Fatalf("canceled inspection emitted disassembly: %q", stdout) + } +} diff --git a/cmd/internal/run/run_test.go b/cmd/internal/run/run_test.go index 1a5fd41..623e662 100644 --- a/cmd/internal/run/run_test.go +++ b/cmd/internal/run/run_test.go @@ -27,7 +27,7 @@ func TestExecuteRun_ArtifactRemoteRuntimeRejected(t *testing.T) { testutil.WriteQuery(t, input, "RETURN 42") - if err := build.WriteArtifact(testutil.NewCompiler(t), source.New(input, "RETURN 42"), artifactPath); err != nil { + if err := build.WriteArtifact(t.Context(), testutil.NewCompiler(t), source.New(input, "RETURN 42"), artifactPath); err != nil { t.Fatalf("build artifact: %v", err) } @@ -58,7 +58,7 @@ func TestExecuteRun_ArtifactStdinRemoteRuntimeRejected(t *testing.T) { testutil.WriteQuery(t, input, "RETURN 42") - if err := build.WriteArtifact(testutil.NewCompiler(t), source.New(input, "RETURN 42"), artifactPath); err != nil { + if err := build.WriteArtifact(t.Context(), testutil.NewCompiler(t), source.New(input, "RETURN 42"), artifactPath); err != nil { t.Fatalf("build artifact: %v", err) } diff --git a/go.mod b/go.mod index d45311c..4ea63b9 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/MontFerret/contrib/modules/web/sitemap v1.0.0-rc.16 github.com/MontFerret/contrib/modules/xml v1.0.0-rc.16 github.com/MontFerret/contrib/modules/yaml v1.0.0-rc.16 - github.com/MontFerret/ferret/v2 v2.0.0-alpha.53 + github.com/MontFerret/ferret/v2 v2.0.0-alpha.54 github.com/MontFerret/specs v1.12.0 github.com/antlr4-go/antlr/v4 v4.13.1 github.com/chzyer/readline v1.5.1 @@ -38,14 +38,15 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 - golang.org/x/mod v0.40.0 - golang.org/x/sync v0.22.0 + golang.org/x/mod v0.41.0 + golang.org/x/sync v0.23.0 ) require ( github.com/BurntSushi/toml v1.6.0 // indirect github.com/JohannesKaufmann/dom v0.3.1 // indirect github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2 // indirect + github.com/MontFerret/api v1.0.0-alpha.16 // indirect github.com/MontFerret/contrib/pkg/common v0.2.0 // indirect github.com/MontFerret/cssx v0.2.0 // indirect github.com/PuerkitoBio/goquery v1.13.0 // indirect @@ -69,14 +70,14 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.10.0 // indirect + github.com/jackc/pgx/v5 v5.11.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/ledongthuc/pdf v0.0.0-20260903153007-b3c860c23753 // indirect + github.com/ledongthuc/pdf v0.0.0-20260907135840-6c8c28e0e8a0 // indirect github.com/mafredri/cdp v0.35.0 // indirect github.com/mattn/go-colorable v0.1.15 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect - github.com/openai/openai-go/v3 v3.56.0 // indirect + github.com/openai/openai-go/v3 v3.61.0 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/richardlehane/mscfb v1.0.8 // indirect @@ -98,19 +99,19 @@ require ( github.com/xuri/excelize/v2 v2.11.0 // indirect github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect github.com/yuin/goldmark v1.8.5 // indirect - github.com/ziflex/dbx v1.10.0 // indirect - github.com/ziflex/go-options v1.5.0 // indirect + github.com/ziflex/dbx v1.11.0 // indirect + github.com/ziflex/go-options v1.6.1 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/crypto v0.56.0 // indirect - golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa // indirect - golang.org/x/net v0.58.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/text v0.41.0 // indirect - golang.org/x/tools v0.49.0 // indirect + golang.org/x/crypto v0.57.0 // indirect + golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect + golang.org/x/net v0.59.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/text v0.42.0 // indirect + golang.org/x/tools v0.50.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect modernc.org/libc v1.75.7 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.12.1 // indirect - modernc.org/sqlite v1.58.0 // indirect + modernc.org/sqlite v1.59.0 // indirect ) diff --git a/go.sum b/go.sum index 5647011..5b976c5 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2 h1:XFJZFWESIWlUEHHjzBuv8R github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2/go.mod h1:BHWO8lJzttJLqwuV8Rb1B3OG2OSzLbssZDI1FRg2eAA= github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/MontFerret/api v1.0.0-alpha.16 h1:OhAyPzy3s57BHiI0mHn4HLv456985nmWCtQgSdUESXg= +github.com/MontFerret/api v1.0.0-alpha.16/go.mod h1:wz5bfUIGYkED8Hqz9CdsW0o2jSgagotbVLOCbHHacSI= github.com/MontFerret/barn v1.4.0 h1:68L+WFVxqPKuxuAj5f8CLBDmo/xnv6q0sxl4+GrrDmc= github.com/MontFerret/barn v1.4.0/go.mod h1:7+5uWWREpGgGppWjiEf8qCQuZyf+QshDr8zy4XaFSlA= github.com/MontFerret/contrib/modules/ai/llm v1.0.0-rc.6 h1:d3F2WWcGdTmYVXfNTTFVXDEonWpTSfmcde2GzOFsGgE= @@ -48,8 +50,8 @@ github.com/MontFerret/contrib/pkg/common v0.2.0 h1:/ftEguEPPg7tGTAHUkTzu4y+nW234 github.com/MontFerret/contrib/pkg/common v0.2.0/go.mod h1:bgwvpoX6xgEIWRQvEmftgsgbs2JER0DHqMqXqyuWq6E= github.com/MontFerret/cssx v0.2.0 h1:De0C6Irbg+qgFPXgWmPpVnwD4RRYUBQSbIYFTUVCNWU= github.com/MontFerret/cssx v0.2.0/go.mod h1:fmGtRUNVaeJYpiPSDlNIbbYzb3+K8NxmNmJOYqlHATU= -github.com/MontFerret/ferret/v2 v2.0.0-alpha.53 h1:vPvCP77cHBIFGCOuG9q4QCTyDXLjGTJBtBw+J11+mk8= -github.com/MontFerret/ferret/v2 v2.0.0-alpha.53/go.mod h1:2wwh8BHG5VHS5Ev5AyeRdDi5VT3NpjBT11UnkcCsvcg= +github.com/MontFerret/ferret/v2 v2.0.0-alpha.54 h1:Csrf/CEsqmuSp0s/nDbnnkmXQtIHuWVLCuzGNNUlFu4= +github.com/MontFerret/ferret/v2 v2.0.0-alpha.54/go.mod h1:6okTqyD30H5pCa8yXHQfpEzgRZWAsHgx68K6DTcDHek= github.com/MontFerret/specs v1.12.0 h1:1HaElGLN+tyZIsrb3WVdulxbW5W0LXEdOH0k6F1sFzk= github.com/MontFerret/specs v1.12.0/go.mod h1:ENGyEPFjrWVPWOUAsBMc1acUsqpswieTrAJfhcm576E= github.com/PuerkitoBio/goquery v1.13.0 h1:mqHbjD7Jmnul4DTR24LKTjo1uUmHUh072kteGV+xpFM= @@ -130,8 +132,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= -github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg= +github.com/jackc/pgx/v5 v5.11.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jarcoal/httpmock v1.4.2 h1:dKwiP/9zITCPfBLsDn3kchbSOu16JrnxtVEmL0fPRcI= @@ -142,8 +144,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/ledongthuc/pdf v0.0.0-20260903153007-b3c860c23753 h1:kk7VDDuLUTRXD5pfRMRt8z9ZXONcEQSPB3VzJMeFObA= -github.com/ledongthuc/pdf v0.0.0-20260903153007-b3c860c23753/go.mod h1:1fEHWurg7pvf5SG6XNE5Q8UZmOwex51Mkx3SLhrW5B4= +github.com/ledongthuc/pdf v0.0.0-20260907135840-6c8c28e0e8a0 h1:7Q+xNAZFmnfYOMweHN3c/PDFUKKfY1pVJ26K++QvVfU= +github.com/ledongthuc/pdf v0.0.0-20260907135840-6c8c28e0e8a0/go.mod h1:1fEHWurg7pvf5SG6XNE5Q8UZmOwex51Mkx3SLhrW5B4= github.com/mafredri/cdp v0.35.0 h1:fKQ6LbcH3WsxVrWbi/DSgLunJTqmF5o/7w8iFDDj71c= github.com/mafredri/cdp v0.35.0/go.mod h1:xS8dVzwKfYswsOHG05SfDCbhNrO89kWVJyMj5vD+zYo= github.com/mafredri/go-lint v0.0.0-20180911205320-920981dfc79e/go.mod h1:k/zdyxI3q6dup24o8xpYjJKTCf2F7rfxLp6w/efTiWs= @@ -159,8 +161,8 @@ github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4 github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/openai/openai-go/v3 v3.56.0 h1:Xb+gsS7Gsb5dx+Uat9BHTWhifAOvQjU+pQNWT/HXeWQ= -github.com/openai/openai-go/v3 v3.56.0/go.mod h1:ufI1+K+t0ijRB3gk8eztiw1crcDpsBuxRQL4sbLIrts= +github.com/openai/openai-go/v3 v3.61.0 h1:nMLuGFdKBF0sB3qFVNwE8kpBpenVN1ucYRoKcx7E1u0= +github.com/openai/openai-go/v3 v3.61.0/go.mod h1:ufI1+K+t0ijRB3gk8eztiw1crcDpsBuxRQL4sbLIrts= github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -238,10 +240,10 @@ github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/T github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= -github.com/ziflex/dbx v1.10.0 h1:g01W014KlFdtbppGz8duw/sIa13FVsv+eWhIw78Oe+Y= -github.com/ziflex/dbx v1.10.0/go.mod h1:AhsraXQs5YJwuSk1IIL5GzVQmcBznlkXsc1QySA9+aw= -github.com/ziflex/go-options v1.5.0 h1:exzcFILJiAeCEH90ZvHVddUa+4d5WXltJBNtExmczH4= -github.com/ziflex/go-options v1.5.0/go.mod h1:91KFoEzT1mtEHOoUImFOnoOEAwSn+LvbSECTVoZTlas= +github.com/ziflex/dbx v1.11.0 h1:C+3Bs8iPg8+JkrXGDrY81w5sWOegLsDpNAa/jeooVX8= +github.com/ziflex/dbx v1.11.0/go.mod h1:3mfEEfWyYkI1kgsA9VZ/Om/KeV7114tah4ON6ll+SqI= +github.com/ziflex/go-options v1.6.1 h1:CCIn24/7lTj5Ju5cM9zxqVwgGIiZqzjmkGYjSQG8U2U= +github.com/ziflex/go-options v1.6.1/go.mod h1:r2mjyrYxJ9waL5zVmAoX8aBOq7pZ3LoGku4rn8/8gxk= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= @@ -251,10 +253,10 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= -golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= -golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa h1:QSyA8ishJCyT21kER9KwNt0b7BM3iRK4x9QXhjN5Fdk= -golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU= +golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E= golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE= golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -262,8 +264,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= -golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -273,8 +275,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= +golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -283,8 +285,8 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -297,8 +299,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -318,16 +320,16 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= -golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= +golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU= +golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= @@ -359,8 +361,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.58.0 h1:38u40/bwkfM7f0Myhosl+SEMltSDxnGdQf8o6Kjmys0= -modernc.org/sqlite v1.58.0/go.mod h1:rsD2CckafgObKC4DhBlGBf+RiHxkc3hINGt1Xw32tVY= +modernc.org/sqlite v1.59.0 h1:X1es1GpqBlS/5T+vbM4HLUdaa8OtQx468DF2vrx+38A= +modernc.org/sqlite v1.59.0/go.mod h1:+paeT2A3iPRHkQDwG7oA6Tk0zQd5woMEI8q7orfry8k= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/pkg/build/artifact.go b/pkg/build/artifact.go index a25e2b8..fbbd412 100644 --- a/pkg/build/artifact.go +++ b/pkg/build/artifact.go @@ -1,6 +1,7 @@ package build import ( + "context" "fmt" "os" "path/filepath" @@ -12,7 +13,9 @@ import ( var renameArtifactFile = os.Rename -func WriteArtifact(c *compiler.Compiler, src source.Source, outputPath string) error { +// WriteArtifact compiles src with a non-nil context and atomically replaces the +// output artifact. Compilation errors leave the destination untouched. +func WriteArtifact(ctx context.Context, c *compiler.Compiler, src source.Source, outputPath string) error { same, err := samePath(src.Name(), outputPath) if err != nil { @@ -23,8 +26,7 @@ func WriteArtifact(c *compiler.Compiler, src source.Source, outputPath string) e return fmt.Errorf("output path %s would overwrite source file %s", outputPath, src.Name()) } - program, err := c.Compile(src) - + program, err := c.Compile(ctx, src) if err != nil { return err } diff --git a/pkg/build/build_test.go b/pkg/build/build_test.go index 92823cd..78c662f 100644 --- a/pkg/build/build_test.go +++ b/pkg/build/build_test.go @@ -1,6 +1,8 @@ package build import ( + "bytes" + "context" "errors" "os" "path/filepath" @@ -197,7 +199,7 @@ func TestWriteArtifact_RejectsOverwritingSource(t *testing.T) { writeQuery(t, input, query) - err := WriteArtifact(newCompiler(t), source.New(input, query), input) + err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, query), input) if err == nil { t.Fatal("expected error") @@ -224,7 +226,7 @@ func TestWriteArtifact_InvalidQueryDoesNotCreateArtifact(t *testing.T) { writeQuery(t, input, "FOR item IN") - err := WriteArtifact(newCompiler(t), source.New(input, "FOR item IN"), output) + err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "FOR item IN"), output) if err == nil { t.Fatal("expected error") @@ -242,7 +244,7 @@ func TestWriteArtifact_CreatesMissingParentDirectory(t *testing.T) { writeQuery(t, input, "RETURN 42") - if err := WriteArtifact(newCompiler(t), source.New(input, "RETURN 42"), output); err != nil { + if err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "RETURN 42"), output); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -256,7 +258,7 @@ func TestWriteArtifact_ReplacesExistingDestinationFile(t *testing.T) { writeQuery(t, input, "RETURN 1") - if err := WriteArtifact(newCompiler(t), source.New(input, "RETURN 1"), output); err != nil { + if err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "RETURN 1"), output); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -264,7 +266,7 @@ func TestWriteArtifact_ReplacesExistingDestinationFile(t *testing.T) { writeQuery(t, input, "RETURN 2") - if err := WriteArtifact(newCompiler(t), source.New(input, "RETURN 2"), output); err != nil { + if err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "RETURN 2"), output); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -278,7 +280,7 @@ func TestWriteArtifact_ReplacesExistingDestinationFileInNestedDirectory(t *testi writeQuery(t, input, "RETURN 1") - if err := WriteArtifact(newCompiler(t), source.New(input, "RETURN 1"), output); err != nil { + if err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "RETURN 1"), output); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -286,7 +288,7 @@ func TestWriteArtifact_ReplacesExistingDestinationFileInNestedDirectory(t *testi writeQuery(t, input, "RETURN 2") - if err := WriteArtifact(newCompiler(t), source.New(input, "RETURN 2"), output); err != nil { + if err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "RETURN 2"), output); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -300,7 +302,7 @@ func TestWriteArtifact_RenameFailurePreservesExistingDestinationAndCleansTemp(t writeQuery(t, input, "RETURN 1") - if err := WriteArtifact(newCompiler(t), source.New(input, "RETURN 1"), output); err != nil { + if err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "RETURN 1"), output); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -311,7 +313,7 @@ func TestWriteArtifact_RenameFailurePreservesExistingDestinationAndCleansTemp(t }) defer restore() - err := WriteArtifact(newCompiler(t), source.New(input, "RETURN 2"), output) + err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "RETURN 2"), output) if err == nil { t.Fatal("expected error") @@ -337,7 +339,7 @@ func TestWriteArtifact_RenameFailureDoesNotCreateDestinationAndCleansTemp(t *tes }) defer restore() - err := WriteArtifact(newCompiler(t), source.New(input, "RETURN 42"), output) + err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "RETURN 42"), output) if err == nil { t.Fatal("expected error") @@ -357,7 +359,7 @@ func TestWriteArtifact_ArtifactRoundTrip(t *testing.T) { writeQuery(t, input, "RETURN 42") - if err := WriteArtifact(newCompiler(t), source.New(input, "RETURN 42"), output); err != nil { + if err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "RETURN 42"), output); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -371,7 +373,7 @@ func TestWriteArtifact_InvalidQueryDoesNotCreateArtifactInMissingParentDirectory writeQuery(t, input, "FOR item IN") - err := WriteArtifact(newCompiler(t), source.New(input, "FOR item IN"), output) + err := WriteArtifact(t.Context(), newCompiler(t), source.New(input, "FOR item IN"), output) if err == nil { t.Fatal("expected error") @@ -382,6 +384,78 @@ func TestWriteArtifact_InvalidQueryDoesNotCreateArtifactInMissingParentDirectory } } +func TestWriteArtifact_CanceledContextLeavesDestinationUntouched(t *testing.T) { + tests := []struct { + name string + missingParent bool + existing bool + }{ + {name: "missing destination"}, + {name: "missing parent", missingParent: true}, + {name: "existing destination", existing: true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + dir := t.TempDir() + input := filepath.Join(dir, "query.fql") + output := filepath.Join(dir, "dist", "query.fqlc") + c := newCompiler(t) + + writeQuery(t, input, "RETURN 2") + + if !test.missingParent { + if err := os.MkdirAll(filepath.Dir(output), 0o755); err != nil { + t.Fatal(err) + } + } + + var original []byte + + if test.existing { + if err := WriteArtifact(t.Context(), c, source.New(input, "RETURN 1"), output); err != nil { + t.Fatal(err) + } + + var err error + original, err = os.ReadFile(output) + if err != nil { + t.Fatal(err) + } + } + + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + err := WriteArtifact(ctx, c, source.New(input, "RETURN 2"), output) + if !errors.Is(err, context.Canceled) { + t.Fatalf("expected cancellation, got %v", err) + } + + if test.existing { + content, err := os.ReadFile(output) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(content, original) { + t.Fatal("canceled compilation replaced the existing artifact") + } + } else if _, err := os.Stat(output); !os.IsNotExist(err) { + t.Fatalf("expected artifact to be absent, got %v", err) + } + + if test.missingParent { + if _, err := os.Stat(filepath.Dir(output)); !os.IsNotExist(err) { + t.Fatalf("expected parent directory to be absent, got %v", err) + } + } + + assertNoTempArtifacts(t, filepath.Dir(output), output) + }) + } +} + func writeQuery(t *testing.T, path, content string) { t.Helper() diff --git a/pkg/debugger/command.go b/pkg/debugger/command.go index 1799d5f..5ec212e 100644 --- a/pkg/debugger/command.go +++ b/pkg/debugger/command.go @@ -109,7 +109,7 @@ const breakpointUsage = "usage: break [--exact|--next|--in-function] [:= 0 { - location.File = prefix[:previousColon] + location.SourceName = prefix[:previousColon] - if location.File == "" { + if location.SourceName == "" { return ferret.DebugSourceLocation{}, errors.New(breakpointUsage) } } @@ -213,7 +213,7 @@ func parseBreakpointLocation(value string) (ferret.DebugSourceLocation, error) { return location, nil } - location.File = prefix + location.SourceName = prefix location.Line = last return location, nil diff --git a/pkg/debugger/command_test.go b/pkg/debugger/command_test.go index a293de7..26b674c 100644 --- a/pkg/debugger/command_test.go +++ b/pkg/debugger/command_test.go @@ -22,7 +22,7 @@ func TestParseCommand(t *testing.T) { Name: CommandBreak, Argument: "12", Location: ferret.DebugSourceLocation{Position: ferret.Position{Line: 12}}, - BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInFile}, + BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInSource}, }, }, { @@ -32,7 +32,7 @@ func TestParseCommand(t *testing.T) { Name: CommandBreak, Argument: "12:4", Location: ferret.DebugSourceLocation{Position: ferret.Position{Line: 12, Column: 4}}, - BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInFile}, + BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInSource}, }, }, { @@ -41,8 +41,8 @@ func TestParseCommand(t *testing.T) { want: Command{ Name: CommandBreak, Argument: "examples/demo.fql:12", - Location: ferret.DebugSourceLocation{File: "examples/demo.fql", Position: ferret.Position{Line: 12}}, - BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInFile}, + Location: ferret.DebugSourceLocation{SourceName: "examples/demo.fql", Position: ferret.Position{Line: 12}}, + BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInSource}, }, }, { @@ -51,8 +51,8 @@ func TestParseCommand(t *testing.T) { want: Command{ Name: CommandBreak, Argument: "examples/demo.fql:12:4", - Location: ferret.DebugSourceLocation{File: "examples/demo.fql", Position: ferret.Position{Line: 12, Column: 4}}, - BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInFile}, + Location: ferret.DebugSourceLocation{SourceName: "examples/demo.fql", Position: ferret.Position{Line: 12, Column: 4}}, + BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInSource}, }, }, { @@ -61,8 +61,8 @@ func TestParseCommand(t *testing.T) { want: Command{ Name: CommandBreak, Argument: `C:\work\demo.fql:12:4`, - Location: ferret.DebugSourceLocation{File: `C:\work\demo.fql`, Position: ferret.Position{Line: 12, Column: 4}}, - BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInFile}, + Location: ferret.DebugSourceLocation{SourceName: `C:\work\demo.fql`, Position: ferret.Position{Line: 12, Column: 4}}, + BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInSource}, }, }, { @@ -82,7 +82,7 @@ func TestParseCommand(t *testing.T) { Name: CommandBreak, Argument: "12 --next", Location: ferret.DebugSourceLocation{Position: ferret.Position{Line: 12}}, - BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInFile}, + BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInSource}, }, }, { @@ -91,7 +91,7 @@ func TestParseCommand(t *testing.T) { want: Command{ Name: CommandBreak, Argument: "--in-function demo.fql:12", - Location: ferret.DebugSourceLocation{File: "demo.fql", Position: ferret.Position{Line: 12}}, + Location: ferret.DebugSourceLocation{SourceName: "demo.fql", Position: ferret.Position{Line: 12}}, BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInFunction}, }, }, @@ -115,7 +115,7 @@ func TestParseCommand(t *testing.T) { Name: CommandBreak, Argument: "4", Location: ferret.DebugSourceLocation{Position: ferret.Position{Line: 4}}, - BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInFile}, + BreakpointOptions: ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInSource}, }, }, {name: "continue alias", input: "c", want: Command{Name: CommandContinue}}, diff --git a/pkg/debugger/prompt.go b/pkg/debugger/prompt.go index e87722a..49c77d8 100644 --- a/pkg/debugger/prompt.go +++ b/pkg/debugger/prompt.go @@ -111,15 +111,15 @@ func Run(ctx context.Context, session Session, src source.Source, input LineRead } } -func executeCommand(ctx context.Context, session Session, mainFile string, renderer *Renderer, command Command) (bool, *ferret.DebugEvent) { +func executeCommand(ctx context.Context, session Session, mainSourceName string, renderer *Renderer, command Command) (bool, *ferret.DebugEvent) { switch command.Name { case CommandHelp: renderer.Help() case CommandBreak: location := command.Location - if location.File == "" { - location.File = mainFile + if location.SourceName == "" { + location.SourceName = mainSourceName } breakpoint, err := session.SetBreakpointAt(location, command.BreakpointOptions) @@ -145,13 +145,13 @@ func executeCommand(ctx context.Context, session Session, mainFile string, rende event, err := session.Continue(ctx) return false, renderResume(event, err, renderer) case CommandStep: - event, err := session.Step(ctx) + event, err := session.StepIn(ctx) return false, renderResume(event, err, renderer) case CommandNext: - event, err := session.Next(ctx) + event, err := session.StepOver(ctx) return false, renderResume(event, err, renderer) case CommandOut: - event, err := session.Out(ctx) + event, err := session.StepOut(ctx) return false, renderResume(event, err, renderer) case CommandPause: if err := session.Pause(); err != nil { diff --git a/pkg/debugger/prompt_test.go b/pkg/debugger/prompt_test.go index 7a1d139..9cf664f 100644 --- a/pkg/debugger/prompt_test.go +++ b/pkg/debugger/prompt_test.go @@ -440,17 +440,17 @@ func (f *fakeSession) Continue(context.Context) (*ferret.DebugEvent, error) { return f.continueEvent, f.continueErr } -func (f *fakeSession) Step(context.Context) (*ferret.DebugEvent, error) { +func (f *fakeSession) StepIn(context.Context) (*ferret.DebugEvent, error) { f.stepCalls++ return f.continueEvent, nil } -func (f *fakeSession) Next(context.Context) (*ferret.DebugEvent, error) { +func (f *fakeSession) StepOver(context.Context) (*ferret.DebugEvent, error) { f.nextCalls++ return f.continueEvent, nil } -func (f *fakeSession) Out(context.Context) (*ferret.DebugEvent, error) { +func (f *fakeSession) StepOut(context.Context) (*ferret.DebugEvent, error) { f.outCalls++ return f.continueEvent, nil } @@ -467,7 +467,7 @@ func (f *fakeSession) SetBreakpointAt(location ferret.DebugSourceLocation, optio breakpoint := ferret.DebugBreakpoint{ ID: ferret.DebugBreakpointID(len(f.breakpoints) + 1), RequestedLocation: location, - Location: debugLocation(location.File, location.Line, location.Column, source.Span{}), + Location: debugLocation(location.SourceName, location.Line, location.Column, source.Span{}), BindingMode: options.BindingMode, Bound: true, } diff --git a/pkg/debugger/render.go b/pkg/debugger/render.go index 6e3daeb..c8d788c 100644 --- a/pkg/debugger/render.go +++ b/pkg/debugger/render.go @@ -189,7 +189,7 @@ func (r *Renderer) error(err error) { } func (r *Renderer) snippet(location ferret.DebugLocation) { - if location.File != r.source.Name() || location.Line <= 0 { + if location.SourceName != r.source.Name() || location.Line <= 0 { return } @@ -238,12 +238,12 @@ func formatBindingMode(mode ferret.DebugBreakpointBindingMode) string { func formatLocation(location source.Location) string { if location.Column > 0 { - return fmt.Sprintf("%s:%d:%d", location.File, location.Line, location.Column) + return fmt.Sprintf("%s:%d:%d", location.SourceName, location.Line, location.Column) } if location.Line > 0 { - return fmt.Sprintf("%s:%d", location.File, location.Line) + return fmt.Sprintf("%s:%d", location.SourceName, location.Line) } - return location.File + return location.SourceName } diff --git a/pkg/debugger/render_test.go b/pkg/debugger/render_test.go index 0a1aeda..23a0508 100644 --- a/pkg/debugger/render_test.go +++ b/pkg/debugger/render_test.go @@ -48,7 +48,7 @@ func TestRendererCollectionsAndErrors(t *testing.T) { ID: 1, RequestedLocation: debugSourceLocation("demo.fql", 4, 3), Location: debugLocation("demo.fql", 7, 5, source.Span{}), - BindingMode: ferret.DebugBreakpointBindNextExecutableInFile, + BindingMode: ferret.DebugBreakpointBindNextExecutableInSource, Bound: true, }, { @@ -164,7 +164,7 @@ func TestRendererHelpIncludesAliasesAndPauseBehavior(t *testing.T) { func debugSourceLocation(file string, line, column int) ferret.DebugSourceLocation { return ferret.DebugSourceLocation{ - File: file, + SourceName: file, Position: ferret.Position{ Line: line, Column: column, diff --git a/pkg/debugger/session.go b/pkg/debugger/session.go index 05d3f44..6bfe2e1 100644 --- a/pkg/debugger/session.go +++ b/pkg/debugger/session.go @@ -7,12 +7,14 @@ import ( ) type ( + // Session is the core debugger contract consumed by the CLI. Run closes the + // session when the interactive loop exits. Session interface { Start(context.Context) (*ferret.DebugEvent, error) Continue(context.Context) (*ferret.DebugEvent, error) - Step(context.Context) (*ferret.DebugEvent, error) - Next(context.Context) (*ferret.DebugEvent, error) - Out(context.Context) (*ferret.DebugEvent, error) + StepIn(context.Context) (*ferret.DebugEvent, error) + StepOver(context.Context) (*ferret.DebugEvent, error) + StepOut(context.Context) (*ferret.DebugEvent, error) Pause() error SetBreakpointAt(ferret.DebugSourceLocation, ferret.DebugBreakpointOptions) (ferret.DebugBreakpoint, error) DeleteBreakpoint(ferret.DebugBreakpointID) error diff --git a/pkg/run/execute.go b/pkg/run/execute.go index b068239..5538808 100644 --- a/pkg/run/execute.go +++ b/pkg/run/execute.go @@ -5,6 +5,8 @@ import ( "fmt" "io" + "github.com/MontFerret/ferret/v2/pkg/source" + cliruntime "github.com/MontFerret/cli/v2/pkg/runtime" ) @@ -17,8 +19,8 @@ func Execute(ctx context.Context, opts cliruntime.Options, params map[string]any return cliruntime.RunArtifact(ctx, opts, input.Artifact, params) } - if input.Source.Empty() { - return nil, fmt.Errorf("run source is empty") + if input.Source.ID() == (source.ID{}) { + return nil, fmt.Errorf("run source is not set") } return cliruntime.Run(ctx, opts, input.Source, params) diff --git a/pkg/run/run_test.go b/pkg/run/run_test.go index 812012b..a0ebee0 100644 --- a/pkg/run/run_test.go +++ b/pkg/run/run_test.go @@ -351,7 +351,7 @@ func buildArtifact(t *testing.T, inputPath, outputPath string) { src := readSource(t, inputPath) - if err := build.WriteArtifact(newCompiler(t), src, outputPath); err != nil { + if err := build.WriteArtifact(t.Context(), newCompiler(t), src, outputPath); err != nil { t.Fatalf("build artifact: %v", err) } } diff --git a/pkg/runtime/debug_test.go b/pkg/runtime/debug_test.go index 066e79f..d83f677 100644 --- a/pkg/runtime/debug_test.go +++ b/pkg/runtime/debug_test.go @@ -26,7 +26,7 @@ func TestNewDebugSessionIntegration(t *testing.T) { nextBreakpoint, err := session.SetBreakpointAt( ferret.DebugSourceLocation{Position: ferret.Position{Line: 3}}, - ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInFile}, + ferret.DebugBreakpointOptions{BindingMode: ferret.DebugBreakpointBindNextExecutableInSource}, ) if err != nil { t.Fatal(err) @@ -128,7 +128,7 @@ func TestNewDebugSessionDocumentSurvivesResume(t *testing.T) { t.Fatalf("unexpected start event: %#v", event) } - event, err = session.Next(ctx) + event, err = session.StepOver(ctx) if err != nil { t.Fatal(err) }