package python import ( "bytes" "path/filepath" "testing" "github.com/databricks/cli/libs/diag" "github.com/stretchr/testify/require" "github.com/databricks/cli/libs/dyn" assert "github.com/databricks/cli/libs/dyn/dynassert" ) func TestMergeLocations(t *testing.T) { pythonLocation := dyn.Location{File: "foo.py", Line: 1, Column: 1} generatedLocation := dyn.Location{File: generatedFileName, Line: 1, Column: 1} yamlLocation := dyn.Location{File: "foo.yml", Line: 1, Column: 1} locations := newPythonLocations() putPythonLocation(locations, dyn.MustPathFromString("foo"), pythonLocation) input := dyn.NewValue( map[string]dyn.Value{ "foo": dyn.NewValue( map[string]dyn.Value{ "baz": dyn.NewValue("baz", []dyn.Location{yamlLocation}), "qux": dyn.NewValue("baz", []dyn.Location{generatedLocation, yamlLocation}), }, []dyn.Location{}, ), "bar": dyn.NewValue("baz", []dyn.Location{generatedLocation}), }, []dyn.Location{yamlLocation}, ) expected := dyn.NewValue( map[string]dyn.Value{ "foo": dyn.NewValue( map[string]dyn.Value{ // pythonLocation is appended to the beginning of the list if absent "baz": dyn.NewValue("baz", []dyn.Location{pythonLocation, yamlLocation}), // generatedLocation is replaced by pythonLocation "qux": dyn.NewValue("baz", []dyn.Location{pythonLocation, yamlLocation}), }, []dyn.Location{pythonLocation}, ), // if location is unknown, we keep it as-is "bar": dyn.NewValue("baz", []dyn.Location{generatedLocation}), }, []dyn.Location{yamlLocation}, ) actual, err := mergePythonLocations(input, locations) assert.NoError(t, err) assert.Equal(t, expected, actual) } func TestFindLocation(t *testing.T) { location0 := dyn.Location{File: "foo.py", Line: 1, Column: 1} location1 := dyn.Location{File: "foo.py", Line: 2, Column: 1} locations := newPythonLocations() putPythonLocation(locations, dyn.MustPathFromString("foo"), location0) putPythonLocation(locations, dyn.MustPathFromString("foo.bar"), location1) actual, exists := findPythonLocation(locations, dyn.MustPathFromString("foo.bar")) assert.True(t, exists) assert.Equal(t, location1, actual) } func TestFindLocation_indexPathComponent(t *testing.T) { location0 := dyn.Location{File: "foo.py", Line: 1, Column: 1} location1 := dyn.Location{File: "foo.py", Line: 2, Column: 1} location2 := dyn.Location{File: "foo.py", Line: 3, Column: 1} locations := newPythonLocations() putPythonLocation(locations, dyn.MustPathFromString("foo"), location0) putPythonLocation(locations, dyn.MustPathFromString("foo.bar"), location1) putPythonLocation(locations, dyn.MustPathFromString("foo.bar[0]"), location2) actual, exists := findPythonLocation(locations, dyn.MustPathFromString("foo.bar[0]")) assert.True(t, exists) assert.Equal(t, location2, actual) } func TestFindLocation_closestAncestorLocation(t *testing.T) { location0 := dyn.Location{File: "foo.py", Line: 1, Column: 1} location1 := dyn.Location{File: "foo.py", Line: 2, Column: 1} locations := newPythonLocations() putPythonLocation(locations, dyn.MustPathFromString("foo"), location0) putPythonLocation(locations, dyn.MustPathFromString("foo.bar"), location1) actual, exists := findPythonLocation(locations, dyn.MustPathFromString("foo.bar.baz")) assert.True(t, exists) assert.Equal(t, location1, actual) } func TestFindLocation_unknownLocation(t *testing.T) { location0 := dyn.Location{File: "foo.py", Line: 1, Column: 1} location1 := dyn.Location{File: "foo.py", Line: 2, Column: 1} locations := newPythonLocations() putPythonLocation(locations, dyn.MustPathFromString("foo"), location0) putPythonLocation(locations, dyn.MustPathFromString("foo.bar"), location1) _, exists := findPythonLocation(locations, dyn.MustPathFromString("bar")) assert.False(t, exists) } func TestLoadOutput(t *testing.T) { location := dyn.Location{File: "my_job.py", Line: 1, Column: 1} bundleRoot := t.TempDir() output := `{ "resources": { "jobs": { "my_job": { "name": "my_job", "tasks": [ { "task_key": "my_task", "notebook_task": { "notebook_path": "my_notebook" } } ] } } } }` locations := newPythonLocations() putPythonLocation( locations, dyn.MustPathFromString("resources.jobs.my_job"), location, ) value, diags := loadOutput( bundleRoot, bytes.NewReader([]byte(output)), locations, ) assert.Equal(t, diag.Diagnostics{}, diags) name, err := dyn.Get(value, "resources.jobs.my_job.name") require.NoError(t, err) require.Equal(t, []dyn.Location{location}, name.Locations()) // until we implement path normalization, we have to keep locations of values // that change semantic depending on their location // // note: it's important to have absolute path including 'bundleRoot' // because mutator pipeline already has expanded locations into absolute path notebookPath, err := dyn.Get(value, "resources.jobs.my_job.tasks[0].notebook_task.notebook_path") require.NoError(t, err) require.Len(t, notebookPath.Locations(), 1) require.Equal(t, filepath.Join(bundleRoot, generatedFileName), notebookPath.Locations()[0].File) } func TestParsePythonLocations(t *testing.T) { expected := dyn.Location{File: "foo.py", Line: 1, Column: 2} input := `{"path": "foo", "file": "foo.py", "line": 1, "column": 2}` reader := bytes.NewReader([]byte(input)) locations, err := parsePythonLocations(reader) assert.NoError(t, err) assert.True(t, locations.keys["foo"].exists) assert.Equal(t, expected, locations.keys["foo"].location) }