diff --git a/cmd/subcommands/tester.go b/cmd/subcommands/tester.go new file mode 100644 index 00000000..c062e098 --- /dev/null +++ b/cmd/subcommands/tester.go @@ -0,0 +1,44 @@ +//go:build full || manage + +package subcommands + +import ( + "context" + + "github.com/spf13/cobra" + + "lunchpail.io/cmd/options" + "lunchpail.io/pkg/be" + "lunchpail.io/pkg/be/target" + "lunchpail.io/pkg/boot" + "lunchpail.io/pkg/build" +) + +func init() { + if build.IsBuilt() && build.HasTestData() { + var cmd = &cobra.Command{ + Use: "test", + Short: "Run stock tests", + Long: "Run stock tests", + } + + buildOpts, err := options.AddBuildOptions(cmd) + if err != nil { + panic(err) + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + buildOpts.Target.Platform = target.Local + backend, err := be.New(ctx, *buildOpts) + if err != nil { + return err + } + + return boot.Tester{Backend: backend, Options: *buildOpts}.RunAll(ctx) + } + + rootCmd.AddCommand(cmd) + } +} diff --git a/go.mod b/go.mod index cf985733..9b4c5005 100644 --- a/go.mod +++ b/go.mod @@ -199,7 +199,7 @@ require ( google.golang.org/protobuf v1.35.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.16.2 // indirect k8s.io/apiextensions-apiserver v0.31.2 // indirect k8s.io/apiserver v0.31.2 // indirect diff --git a/pkg/boot/io.go b/pkg/boot/io.go index 1ea6d144..d320e76a 100644 --- a/pkg/boot/io.go +++ b/pkg/boot/io.go @@ -18,7 +18,7 @@ import ( ) // Behave like `cat inputs | ... > outputs` -func catAndRedirect(ctx context.Context, inputs []string, backend be.Backend, ir llir.LLIR, noRedirect bool, opts build.LogOptions) error { +func catAndRedirect(ctx context.Context, inputs []string, backend be.Backend, ir llir.LLIR, alldone <-chan struct{}, noRedirect bool, redirectTo string, opts build.LogOptions) error { client, err := s3.NewS3ClientForRun(ctx, backend, ir.Context.Run, opts) if err != nil { return err @@ -69,6 +69,10 @@ func catAndRedirect(ctx context.Context, inputs []string, backend be.Backend, ir // may be a fool's errand, e.g. what if a single input // results in two outputs? folderFor := func(output string) string { + if redirectTo != "" { + // We were asked to redirect to a particular directory + return redirectTo + } inIdx := slices.IndexFunc(inputs, func(in string) bool { return filepath.Base(in) == output }) if inIdx >= 0 { return filepath.Dir(inputs[inIdx]) @@ -78,7 +82,7 @@ func catAndRedirect(ctx context.Context, inputs []string, backend be.Backend, ir if opts.Verbose { fmt.Fprintln(os.Stderr, "up is redirecting output files", os.Args) } - if err := builtins.RedirectTo(ctx, client.S3Client, client.RunContext, folderFor, opts); err != nil { + if err := builtins.RedirectTo(ctx, client.S3Client, client.RunContext, folderFor, alldone, opts); err != nil { return err } } diff --git a/pkg/boot/tester.go b/pkg/boot/tester.go new file mode 100644 index 00000000..80d76dd8 --- /dev/null +++ b/pkg/boot/tester.go @@ -0,0 +1,159 @@ +//go:build full || manage + +package boot + +import ( + "bytes" + "compress/gzip" + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "slices" + + "github.com/dustin/go-humanize/english" + + "lunchpail.io/pkg/be" + "lunchpail.io/pkg/build" + "lunchpail.io/pkg/ir/hlir" +) + +type Tester struct { + be.Backend + build.Options +} + +func (t Tester) RunAll(ctx context.Context) error { + testData, stageDir, err := build.TestDataWithStage() + if err != nil { + return err + } + if !t.Options.Verbose() { + defer os.RemoveAll(stageDir) + } + + inputs, expected, err := t.prepareInputs(testData, stageDir) + if err != nil { + return err + } else if len(inputs) == 0 { + return fmt.Errorf("This application provided no test input") + } + + return t.Run(ctx, inputs, expected) +} + +func (t Tester) prepareInputs(testData hlir.TestData, stageDir string) (inputs []string, outputs []string, err error) { + inputDir := build.TestDataDirForInput(stageDir) + expectedDir := build.TestDataDirForExpected(stageDir) + for _, test := range testData { + inputs = append(inputs, filepath.Join(inputDir, test.Input)) + outputs = append(outputs, filepath.Join(expectedDir, test.Expected)) + } + + if t.Options.Verbose() { + fmt.Fprintln(os.Stderr, "Test data staged to", inputDir) + } + + return +} + +func (t Tester) Run(ctx context.Context, inputs []string, expected []string) error { + fmt.Fprintf(os.Stderr, "Scheduling %s for %s\n", english.Plural(len(inputs), "test", ""), build.Name()) + + if slices.IndexFunc(inputs, func(input string) bool { return filepath.Ext(input) == ".gz" }) >= 0 { + t.Options.Gunzip = true + } + + redirectTo, err := ioutil.TempDir("", build.Name()+"-test-output") + if err != nil { + return err + } + if !t.Options.Verbose() { + defer os.RemoveAll(redirectTo) + } + + if err := Up(ctx, t.Backend, UpOptions{Inputs: inputs, BuildOptions: t.Options, RedirectTo: redirectTo}); err != nil { + return err + } + + if err := t.validate(inputs, expected, redirectTo); err != nil { + fmt.Fprintln(os.Stderr, "❌ FAIL", build.Name(), err) + return err + } + + fmt.Fprintln(os.Stderr, "✅ PASS", build.Name()) + return nil +} + +func (t Tester) validate(inputs []string, expecteds []string, redirectTo string) error { + if len(expecteds) == 0 { + // Nothing to validate + if t.Options.Verbose() { + fmt.Fprintln(os.Stderr, "Skipping validation, as no expected output was provided for", build.Name()) + } + return nil + } + + if t.Options.Verbose() { + fmt.Fprintf(os.Stderr, "Validating output for %s in redirect directory %s\n", build.Name(), redirectTo) + } + + actuals, err := os.ReadDir(redirectTo) + if err != nil { + return err + } + + found := 0 + for idx, expected := range expecteds { + expectedFileName := filepath.Base(inputs[idx]) + + // TODO O(N^2) + for _, actual := range actuals { + matches := actual.Name() == expectedFileName + matchesWithGunzip := !matches && actual.Name()+".gz" == expectedFileName + if matches || matchesWithGunzip { + found++ + + actualBytes, err := os.ReadFile(filepath.Join(redirectTo, actual.Name())) + if err != nil { + return err + } + + expectedBytes, err := os.ReadFile(expected) + if err != nil { + return err + } + + if ok, err := t.equal(matchesWithGunzip, expectedBytes, actualBytes); err != nil { + return err + } else if !ok { + return fmt.Errorf("actual!=expected for %s", filepath.Base(inputs[idx])) + } + } + } + } + + if found != len(expecteds) { + return fmt.Errorf("Missing output files. Expected %d got %d.", len(expecteds), found) + } + + return nil +} + +func (t Tester) equal(needsGunzip bool, expected, actual []byte) (bool, error) { + if needsGunzip { + reader, err := gzip.NewReader(bytes.NewReader(expected)) + if err != nil { + return false, err + } + defer reader.Close() + + expected, err = ioutil.ReadAll(reader) + if err != nil { + return false, err + } + } + + return bytes.Equal(expected, actual), nil +} diff --git a/pkg/boot/up.go b/pkg/boot/up.go index a2ba423e..d3c28b7b 100644 --- a/pkg/boot/up.go +++ b/pkg/boot/up.go @@ -27,6 +27,7 @@ type UpOptions struct { BuildOptions build.Options Executable string NoRedirect bool + RedirectTo string } func Up(ctx context.Context, backend be.Backend, opts UpOptions) error { @@ -153,11 +154,9 @@ func upLLIR(ctx context.Context, backend be.Backend, ir llir.LLIR, opts UpOption // Then Minio went away on its own. That's probably ok. errorFromAllDone = nil } - alldone <- struct{}{} - cancel() - } else { - alldone <- struct{}{} } + alldone <- struct{}{} // once for here + alldone <- struct{}{} // once for redirect } }() @@ -173,7 +172,7 @@ func upLLIR(ctx context.Context, backend be.Backend, ir llir.LLIR, opts UpOption } defer func() { redirectDone <- struct{}{} }() - if err := catAndRedirect(cancellable, opts.Inputs, backend, ir, opts.NoRedirect, *opts.BuildOptions.Log); err != nil { + if err := catAndRedirect(cancellable, opts.Inputs, backend, ir, alldone, opts.NoRedirect, opts.RedirectTo, *opts.BuildOptions.Log); err != nil { errorFromIo = err cancel() } diff --git a/pkg/build/breadcrumbs.go b/pkg/build/breadcrumbs.go index 846a011e..6ffa9ddb 100644 --- a/pkg/build/breadcrumbs.go +++ b/pkg/build/breadcrumbs.go @@ -8,6 +8,10 @@ import ( "path/filepath" "strings" "time" + + "gopkg.in/yaml.v2" + + "lunchpail.io/pkg/ir/hlir" ) //go:embed buildName.txt @@ -25,6 +29,9 @@ var on string //go:embed appVersion.txt var appVersion string +//go:embed testData.yaml +var testData []byte + func Name() string { n := os.Getenv("LUNCHPAIL_NAME") if n == "" { @@ -60,11 +67,46 @@ func AppVersion() string { return strings.TrimSpace(appVersion) } +func HasTestData() bool { + return len(testData) > 0 +} + +func TestData() (data hlir.TestData, err error) { + if !HasTestData() { + return + } + + err = yaml.Unmarshal(testData, &data) + return +} + +func TestDataWithStage() (data hlir.TestData, stagePath string, err error) { + data, err = TestData() + if err != nil { + return + } + + stagePath, err = StageForBuilder(StageOptions{}) + return +} + +func TestDataDirFor(templatePath string) string { + return filepath.Join(templatePath, "test-data") +} + +func TestDataDirForInput(templatePath string) string { + return filepath.Join(TestDataDirFor(templatePath), "input") +} + +func TestDataDirForExpected(templatePath string) string { + return filepath.Join(TestDataDirFor(templatePath), "expected") +} + func IsBuilt() bool { return strings.TrimSpace(name) != "" } -func DropBreadcrumbs(buildName, appVersion string, opts Options, stagedir string) error { +func DropBreadcrumbs(buildName, appVersion string, testData hlir.TestData, opts Options, stagedir string) error { user, err := user.Current() if err != nil { return err @@ -75,6 +117,14 @@ func DropBreadcrumbs(buildName, appVersion string, opts Options, stagedir string return err } + if len(testData) > 0 { + if b, err := yaml.Marshal(testData); err != nil { + return err + } else if err := os.WriteFile(filepath.Join(stagedir, "pkg/build/testData.yaml"), b, 0644); err != nil { + return err + } + } + if err := os.WriteFile(filepath.Join(stagedir, "pkg/build/buildName.txt"), []byte(buildName), 0644); err != nil { return err } else if err := os.WriteFile(filepath.Join(stagedir, "pkg/build/appVersion.txt"), []byte(appVersion), 0644); err != nil { diff --git a/pkg/build/stage.go b/pkg/build/stage.go index 81f5fdb7..26ed704b 100644 --- a/pkg/build/stage.go +++ b/pkg/build/stage.go @@ -15,7 +15,7 @@ type StageOptions struct { } // return (templatePath, error) -func StageForBuilder(appname string, opts StageOptions) (string, error) { +func StageForBuilder(opts StageOptions) (string, error) { // TODO overlay on kube/common? templatePath, err := ioutil.TempDir("", "lunchpail") if err != nil { @@ -34,8 +34,7 @@ func StageForBuilder(appname string, opts StageOptions) (string, error) { // return (templatePath, error) func StageForRun(opts StageOptions) (string, error) { - appname := Name() - templatePath, err := StageForBuilder(appname, opts) + templatePath, err := StageForBuilder(opts) // TODO we could parallelize these two, but the overhead is probably not worth it if err := emitPlaceholderChartYaml(templatePath); err != nil { diff --git a/pkg/build/testData.yaml b/pkg/build/testData.yaml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/pkg/build/testData.yaml @@ -0,0 +1 @@ + diff --git a/pkg/fe/builder/build.go b/pkg/fe/builder/build.go index d28a27a3..80d883e5 100644 --- a/pkg/fe/builder/build.go +++ b/pkg/fe/builder/build.go @@ -50,7 +50,7 @@ func Build(ctx context.Context, sourcePath string, opts Options) error { fmt.Fprintf(os.Stderr, "Building %s\n", buildName) // Third, overlay source (if given) - appTemplatePath, appVersion, err := overlay.OverlaySourceOntoPriorBuild(buildName, sourcePath, opts.OverlayOptions) + appTemplatePath, appVersion, hasTestData, err := overlay.OverlaySourceOntoPriorBuild(buildName, sourcePath, opts.OverlayOptions) if err != nil { return err } @@ -63,7 +63,7 @@ func Build(ctx context.Context, sourcePath string, opts Options) error { } // Fifth, tell the build about itself (its name, version) - if err := build.DropBreadcrumbs(buildName, appVersion, opts.OverlayOptions.BuildOptions, lunchpailStageDir); err != nil { + if err := build.DropBreadcrumbs(buildName, appVersion, hasTestData, opts.OverlayOptions.BuildOptions, lunchpailStageDir); err != nil { return err } diff --git a/pkg/fe/builder/overlay/filesystem.go b/pkg/fe/builder/overlay/filesystem.go index 58bb4c04..a0bf9118 100644 --- a/pkg/fe/builder/overlay/filesystem.go +++ b/pkg/fe/builder/overlay/filesystem.go @@ -12,6 +12,7 @@ import ( "gopkg.in/yaml.v3" + "lunchpail.io/pkg/build" "lunchpail.io/pkg/ir/hlir" ) @@ -22,26 +23,29 @@ type filesystemBuilder struct { // Formulate an HLIR for the source in the given `sourcePath` // filesystem and write it out to the `templatePath` -func copyFilesystemIntoTemplate(appname, sourcePath, templatePath string, opts Options) (appVersion string, err error) { +func copyFilesystemIntoTemplate(appname, sourcePath, templatePath string, opts Options) (appVersion string, testData hlir.TestData, err error) { if opts.Verbose() { fmt.Fprintln(os.Stderr, "Copying application source into", appdir(templatePath)) } - appVersion, app, err := filesystemBuilder{appname, opts.Verbose()}.scan(sourcePath) + var app hlir.Application + appVersion, app, err = filesystemBuilder{appname, opts.Verbose()}.scan(sourcePath, templatePath) if err != nil { - return "", err + return } - b, err := yaml.Marshal(app) + var b []byte + b, err = yaml.Marshal(app) if err != nil { - return "", err + return } - if err := os.WriteFile(filepath.Join(appdir(templatePath), "app.yaml"), b, 0644); err != nil { - return "", err + if err = os.WriteFile(filepath.Join(appdir(templatePath), "app.yaml"), b, 0644); err != nil { + return } - return appVersion, nil + testData = app.Spec.TestData + return } func (_ filesystemBuilder) readString(path string) (string, error) { @@ -53,7 +57,7 @@ func (_ filesystemBuilder) readString(path string) (string, error) { } // Formulate an HLIR for the source in the given `sourcePath` -func (b filesystemBuilder) scan(sourcePath string) (appVersion string, app hlir.Application, err error) { +func (b filesystemBuilder) scan(sourcePath, templatePath string) (appVersion string, app hlir.Application, err error) { app = hlir.NewWorkerApplication(b.appname) spec := &app.Spec @@ -75,6 +79,11 @@ func (b filesystemBuilder) scan(sourcePath string) (appVersion string, app hlir. return } + // Handle test-data/ artifacts + if err = b.addTestData(spec, sourcePath, templatePath); err != nil { + return + } + b.correctPythonNeedsVersion(spec) return @@ -224,6 +233,46 @@ func (b filesystemBuilder) addMetadata(spec *hlir.Spec, sourcePath string) (appV return } +func (b filesystemBuilder) addTestData(spec *hlir.Spec, sourcePath, templatePath string) error { + templateTestDataDir := build.TestDataDirFor(templatePath) + templateTestDataDirForInput := build.TestDataDirForInput(templatePath) + templateTestDataDirForExpected := build.TestDataDirForExpected(templatePath) + + testDataDir := filepath.Join(sourcePath, "test-data") + inputDir := filepath.Join(testDataDir, filepath.Base(templateTestDataDirForInput)) + expectedDir := filepath.Join(testDataDir, filepath.Base(templateTestDataDirForExpected)) + + if d, err := os.Stat(testDataDir); err == nil && d.IsDir() { + if err := os.CopyFS(templateTestDataDir, os.DirFS(testDataDir)); err != nil { + return err + } + } + + if inputs, err := os.ReadDir(inputDir); err == nil { + for _, input := range inputs { + if !input.IsDir() { + test := hlir.TestDatum{Name: input.Name(), Input: input.Name()} + + output := filepath.Join(expectedDir, input.Name()) + if _, err := os.Stat(output); err != nil { + // Then the application does not provided expected output + fmt.Fprintln(os.Stderr, "Warning: expected output not provided for", input.Name()) + } else { + test.Expected = input.Name() + } + + spec.TestData = append(spec.TestData, test) + } + } + } + + if b.verbose && len(spec.TestData) > 0 { + fmt.Fprintf(os.Stderr, "Application provided %d test inputs\n", len(spec.TestData)) + } + + return nil +} + // If we now know the specific python version needed (e.g. because of // a given command or image file), we can update the Needs spec. TODO: // handle version from image. diff --git a/pkg/fe/builder/overlay/overlay.go b/pkg/fe/builder/overlay/overlay.go index 6c72cdd6..a00cb731 100644 --- a/pkg/fe/builder/overlay/overlay.go +++ b/pkg/fe/builder/overlay/overlay.go @@ -4,6 +4,7 @@ import ( "os" "lunchpail.io/pkg/build" + "lunchpail.io/pkg/ir/hlir" ) // This utility combines two pieces: @@ -13,11 +14,11 @@ import ( // Note: we may have been given no source artifacts, if the user has // asked to re-build, only changing or adding --set values but not // changing/adding source -func OverlaySourceOntoPriorBuild(appname, sourcePath string, opts Options) (templatePath string, appVersion string, err error) { +func OverlaySourceOntoPriorBuild(appname, sourcePath string, opts Options) (templatePath string, appVersion string, testData hlir.TestData, err error) { appVersion = build.AppVersion() // 1) stage what was previously built to a local directory, via build.StageAppTemplate() - if templatePath, err = build.StageForBuilder(appname, build.StageOptions{Verbose: opts.Verbose()}); err != nil { + if templatePath, err = build.StageForBuilder(build.StageOptions{Verbose: opts.Verbose()}); err != nil { return } @@ -37,7 +38,7 @@ func OverlaySourceOntoPriorBuild(appname, sourcePath string, opts Options) (temp case sourcePath != "": // 2c) source in the directory, no HLIR yaml given - appVersion, err = copyFilesystemIntoTemplate(appname, sourcePath, templatePath, opts) + appVersion, testData, err = copyFilesystemIntoTemplate(appname, sourcePath, templatePath, opts) } return diff --git a/pkg/ir/hlir/application.go b/pkg/ir/hlir/application.go index 3c63256a..67e233ed 100644 --- a/pkg/ir/hlir/application.go +++ b/pkg/ir/hlir/application.go @@ -30,6 +30,7 @@ type Spec struct { Needs []Needs `yaml:"needs,omitempty"` IsDispatcher bool `yaml:"isDispatcher,omitempty"` CallingConvention `yaml:"callingConvention,omitempty"` + TestData `yaml:"testData,omitempty"` } type Application struct { diff --git a/pkg/ir/hlir/testdata.go b/pkg/ir/hlir/testdata.go new file mode 100644 index 00000000..7fe9e68f --- /dev/null +++ b/pkg/ir/hlir/testdata.go @@ -0,0 +1,9 @@ +package hlir + +type TestDatum struct { + Name string + Input string + Expected string +} + +type TestData = []TestDatum diff --git a/pkg/observe/queuestreamer/stream.go b/pkg/observe/queuestreamer/stream.go index 70ac5fa7..dec3c510 100644 --- a/pkg/observe/queuestreamer/stream.go +++ b/pkg/observe/queuestreamer/stream.go @@ -93,15 +93,15 @@ func once(ctx context.Context, c client, modelChan chan Model, doneChan chan str fmt.Fprintf(os.Stderr, "Queue streamer got push notification object=%s\n", obj) } + // fetch and parse model + modelChan <- c.fetchModel(opts.AnyStep) + if obj == allDoneMarker { if c.LogOptions.Verbose { fmt.Fprintln(os.Stderr, "Queue streamer got all done") } return nil } - - // fetch and parse model - modelChan <- c.fetchModel(opts.AnyStep) } } } diff --git a/pkg/runtime/builtins/redirect.go b/pkg/runtime/builtins/redirect.go index 490a70ef..71ecd7e6 100644 --- a/pkg/runtime/builtins/redirect.go +++ b/pkg/runtime/builtins/redirect.go @@ -15,7 +15,7 @@ import ( s3 "lunchpail.io/pkg/runtime/queue" ) -func RedirectTo(ctx context.Context, client s3.S3Client, run queue.RunContext, folderFor func(object string) string, opts build.LogOptions) error { +func RedirectTo(ctx context.Context, client s3.S3Client, run queue.RunContext, folderFor func(object string) string, alldone <-chan struct{}, opts build.LogOptions) error { outbox := run.AsFile(queue.AssignedAndFinished) outboxObjects, outboxErrs := client.Listen(run.Bucket, outbox, "", false) @@ -35,7 +35,7 @@ func RedirectTo(ctx context.Context, client s3.S3Client, run queue.RunContext, f ext := filepath.Ext(object) withoutExt := object[0 : len(object)-len(ext)] dst2 := filepath.Join(dstFolder, strings.Replace(withoutExt, outbox+"/", "", 1)+".output"+ext) - fmt.Fprintf(os.Stderr, "Refusing to overwrite existing file %s. Using %s instead.", dst, dst2) + fmt.Fprintf(os.Stderr, "Refusing to overwrite existing file %s. Using %s instead.\n", dst, dst2) dst = dst2 } if opts.Verbose { @@ -64,6 +64,8 @@ func RedirectTo(ctx context.Context, client s3.S3Client, run queue.RunContext, f select { case <-ctx.Done(): done = true + case <-alldone: + done = true case err := <-outboxErrs: if err == nil || strings.Contains(err.Error(), "EOF") { done = true diff --git a/pkg/runtime/worker/process-task.go b/pkg/runtime/worker/process-task.go index a5f53e64..cea347c3 100644 --- a/pkg/runtime/worker/process-task.go +++ b/pkg/runtime/worker/process-task.go @@ -116,7 +116,7 @@ func (p taskProcessor) process(task string) error { handlerArgs = append(handlerArgs, p.lockfile) // argv[4] is the lockfile default: - localoutbox := filepath.Join(p.localdir, "outbox") + localoutbox := filepath.Join(p.localdir, "outbox", strings.Replace(task, "/", "_", -1)) err := os.MkdirAll(localoutbox, os.ModePerm) if err != nil { fmt.Fprintln(os.Stderr, "Internal Error creating local outbox:", err) @@ -141,7 +141,7 @@ func (p taskProcessor) process(task string) error { handlerArgs = append(handlerArgs, p.lockfile) // argv[4] is the lockfile // Note: we will RemoveAll(localoutbox) in handleOutbox - defer func() { p.handleOutbox(taskContext, inprogress, localoutbox, doneMovingToProcessing) }() + defer p.handleOutbox(taskContext, inprogress, localoutbox, doneMovingToProcessing) } handlercmd := exec.CommandContext(p.ctx, p.handler[0], handlerArgs...) diff --git a/tests/tests/python-code-code-quality/pail/test-data/expected/sample_1.parquet.gz b/tests/tests/python-code-code-quality/pail/test-data/expected/sample_1.parquet.gz index 6395b679..e3295750 100644 Binary files a/tests/tests/python-code-code-quality/pail/test-data/expected/sample_1.parquet.gz and b/tests/tests/python-code-code-quality/pail/test-data/expected/sample_1.parquet.gz differ diff --git a/tests/tests/python-code-code-quality/pail/test-data/expected/sample_2.parquet.gz b/tests/tests/python-code-code-quality/pail/test-data/expected/sample_2.parquet.gz index 7c995202..e60e71e5 100644 Binary files a/tests/tests/python-code-code-quality/pail/test-data/expected/sample_2.parquet.gz and b/tests/tests/python-code-code-quality/pail/test-data/expected/sample_2.parquet.gz differ