-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
331 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package python | ||
|
||
import ( | ||
"bufio" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"regexp" | ||
"strconv" | ||
|
||
"github.com/databricks/cli/libs/diag" | ||
"github.com/databricks/cli/libs/dyn" | ||
) | ||
|
||
type pythonDiagnostic struct { | ||
Severity pythonSeverity `json:"severity"` | ||
Summary string `json:"summary"` | ||
Detail string `json:"detail,omitempty"` | ||
Location string `json:"location,omitempty"` | ||
Path string `json:"path,omitempty"` | ||
} | ||
|
||
type pythonSeverity = string | ||
|
||
var locationRegex = regexp.MustCompile(`^(.*):(\d+):(\d+)$`) | ||
|
||
const ( | ||
pythonError pythonSeverity = "error" | ||
pythonWarning pythonSeverity = "warning" | ||
) | ||
|
||
// parsePythonDiagnostics parses diagnostics from the Python mutator. | ||
// | ||
// diagnostics file is newline-separated JSON objects with pythonDiagnostic structure. | ||
func parsePythonDiagnostics(input io.Reader) (diag.Diagnostics, error) { | ||
// the default limit is 64 Kb which should be enough for diagnostics | ||
scanner := bufio.NewScanner(input) | ||
diagnostics := diag.Diagnostics{} | ||
|
||
for scanner.Scan() { | ||
line := scanner.Text() | ||
|
||
if line == "" { | ||
continue | ||
} | ||
|
||
parsedLine := pythonDiagnostic{} | ||
|
||
err := json.Unmarshal([]byte(line), &parsedLine) | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse diagnostics: %s", err) | ||
} | ||
|
||
severity, err := convertPythonSeverity(parsedLine.Severity) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse severity: %s", err) | ||
} | ||
|
||
location, err := convertPythonLocation(parsedLine.Location) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse location: %s", err) | ||
} | ||
|
||
path, err := convertPythonPath(parsedLine.Path) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse path: %s", err) | ||
} | ||
|
||
diagnostic := diag.Diagnostic{ | ||
Severity: severity, | ||
Summary: parsedLine.Summary, | ||
Detail: parsedLine.Detail, | ||
Location: location, | ||
Path: path, | ||
} | ||
|
||
diagnostics = diagnostics.Append(diagnostic) | ||
} | ||
|
||
return diagnostics, nil | ||
} | ||
|
||
func convertPythonPath(path string) (dyn.Path, error) { | ||
if path == "" { | ||
return nil, nil | ||
} | ||
|
||
return dyn.NewPathFromString(path) | ||
} | ||
|
||
func convertPythonSeverity(severity pythonSeverity) (diag.Severity, error) { | ||
switch severity { | ||
case pythonError: | ||
return diag.Error, nil | ||
case pythonWarning: | ||
return diag.Warning, nil | ||
default: | ||
return 0, fmt.Errorf("unexpected value: %s", severity) | ||
} | ||
} | ||
|
||
func convertPythonLocation(location string) (dyn.Location, error) { | ||
if location == "" { | ||
return dyn.Location{}, nil | ||
} | ||
|
||
matches := locationRegex.FindStringSubmatch(location) | ||
|
||
if len(matches) == 4 { | ||
line, err := strconv.Atoi(matches[2]) | ||
if err != nil { | ||
return dyn.Location{}, fmt.Errorf("failed to parse line number: %s", location) | ||
} | ||
|
||
column, err := strconv.Atoi(matches[3]) | ||
if err != nil { | ||
return dyn.Location{}, fmt.Errorf("failed to parse column number: %s", location) | ||
} | ||
|
||
return dyn.Location{ | ||
File: matches[1], | ||
Line: line, | ||
Column: column, | ||
}, nil | ||
} | ||
|
||
return dyn.Location{}, fmt.Errorf("failed to parse location: %s", location) | ||
} |
104 changes: 104 additions & 0 deletions
104
bundle/config/mutator/python/python_diagnostics_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package python | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
|
||
"github.com/databricks/cli/libs/diag" | ||
"github.com/databricks/cli/libs/dyn" | ||
assert "github.com/databricks/cli/libs/dyn/dynassert" | ||
) | ||
|
||
func TestConvertPythonLocation(t *testing.T) { | ||
location, err := convertPythonLocation("my:file.py:1:2") | ||
|
||
assert.NoError(t, err) | ||
assert.Equal(t, dyn.Location{ | ||
File: "my:file.py", | ||
Line: 1, | ||
Column: 2, | ||
}, location) | ||
} | ||
|
||
type parsePythonDiagnosticsTest struct { | ||
name string | ||
input string | ||
expected diag.Diagnostics | ||
} | ||
|
||
func TestParsePythonDiagnostics(t *testing.T) { | ||
|
||
testCases := []parsePythonDiagnosticsTest{ | ||
{ | ||
name: "short error with location", | ||
input: `{"severity": "error", "summary": "error summary", "location": "src/examples/file.py:1:2"}`, | ||
expected: diag.Diagnostics{ | ||
{ | ||
Severity: diag.Error, | ||
Summary: "error summary", | ||
Location: dyn.Location{ | ||
File: "src/examples/file.py", | ||
Line: 1, | ||
Column: 2, | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "short error with path", | ||
input: `{"severity": "error", "summary": "error summary", "path": "resources.jobs.job0.name"}`, | ||
expected: diag.Diagnostics{ | ||
{ | ||
Severity: diag.Error, | ||
Summary: "error summary", | ||
Path: dyn.MustPathFromString("resources.jobs.job0.name"), | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "empty file", | ||
input: "", | ||
expected: diag.Diagnostics{}, | ||
}, | ||
{ | ||
name: "newline file", | ||
input: "\n", | ||
expected: diag.Diagnostics{}, | ||
}, | ||
{ | ||
name: "warning with detail", | ||
input: `{"severity": "warning", "summary": "warning summary", "detail": "warning detail"}`, | ||
expected: diag.Diagnostics{ | ||
{ | ||
Severity: diag.Warning, | ||
Summary: "warning summary", | ||
Detail: "warning detail", | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "multiple errors", | ||
input: `{"severity": "error", "summary": "error summary (1)"}` + "\n" + | ||
`{"severity": "error", "summary": "error summary (2)"}`, | ||
expected: diag.Diagnostics{ | ||
{ | ||
Severity: diag.Error, | ||
Summary: "error summary (1)", | ||
}, | ||
{ | ||
Severity: diag.Error, | ||
Summary: "error summary (2)", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
diagnostics, err := parsePythonDiagnostics(bytes.NewReader([]byte(tc.input))) | ||
|
||
assert.NoError(t, err) | ||
assert.Equal(t, tc.expected, diagnostics) | ||
}) | ||
} | ||
} |
Oops, something went wrong.