diff options
Diffstat (limited to 'third_party/go/git-appraise/review/analyses')
-rw-r--r-- | third_party/go/git-appraise/review/analyses/analyses.go | 160 | ||||
-rw-r--r-- | third_party/go/git-appraise/review/analyses/analyses_test.go | 77 |
2 files changed, 237 insertions, 0 deletions
diff --git a/third_party/go/git-appraise/review/analyses/analyses.go b/third_party/go/git-appraise/review/analyses/analyses.go new file mode 100644 index 000000000000..4828f3b230c2 --- /dev/null +++ b/third_party/go/git-appraise/review/analyses/analyses.go @@ -0,0 +1,160 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package analyses defines the internal representation of static analysis reports. +package analyses + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "sort" + "strconv" + + "github.com/google/git-appraise/repository" +) + +const ( + // Ref defines the git-notes ref that we expect to contain analysis reports. + Ref = "refs/notes/devtools/analyses" + + // StatusLooksGoodToMe is the status string representing that analyses reported no messages. + StatusLooksGoodToMe = "lgtm" + // StatusForYourInformation is the status string representing that analyses reported informational messages. + StatusForYourInformation = "fyi" + // StatusNeedsMoreWork is the status string representing that analyses reported error messages. + StatusNeedsMoreWork = "nmw" + + // FormatVersion defines the latest version of the request format supported by the tool. + FormatVersion = 0 +) + +// Report represents a build/test status report generated by analyses tool. +// Every field is optional. +type Report struct { + Timestamp string `json:"timestamp,omitempty"` + URL string `json:"url,omitempty"` + Status string `json:"status,omitempty"` + // Version represents the version of the metadata format. + Version int `json:"v,omitempty"` +} + +// LocationRange represents the location within a source file that an analysis message covers. +type LocationRange struct { + StartLine uint32 `json:"start_line,omitempty"` + StartColumn uint32 `json:"start_column,omitempty"` + EndLine uint32 `json:"end_line,omitempty"` + EndColumn uint32 `json:"end_column,omitempty"` +} + +// Location represents the location within a source tree that an analysis message covers. +type Location struct { + Path string `json:"path,omitempty"` + Range *LocationRange `json:"range,omitempty"` +} + +// Note represents a single analysis message. +type Note struct { + Location *Location `json:"location,omitempty"` + Category string `json:"category,omitempty"` + Description string `json:"description"` +} + +// AnalyzeResponse represents the response from a static-analysis tool. +type AnalyzeResponse struct { + Notes []Note `json:"note,omitempty"` +} + +// ReportDetails represents an entire static analysis run (which might include multiple analysis tools). +type ReportDetails struct { + AnalyzeResponse []AnalyzeResponse `json:"analyze_response,omitempty"` +} + +// GetLintReportResult downloads the details of a lint report and returns the responses embedded in it. +func (analysesReport Report) GetLintReportResult() ([]AnalyzeResponse, error) { + if analysesReport.URL == "" { + return nil, nil + } + res, err := http.Get(analysesReport.URL) + if err != nil { + return nil, err + } + analysesResults, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + return nil, err + } + var details ReportDetails + err = json.Unmarshal([]byte(analysesResults), &details) + if err != nil { + return nil, err + } + return details.AnalyzeResponse, nil +} + +// GetNotes downloads the details of an analyses report and returns the notes embedded in it. +func (analysesReport Report) GetNotes() ([]Note, error) { + reportResults, err := analysesReport.GetLintReportResult() + if err != nil { + return nil, err + } + var reportNotes []Note + for _, reportResult := range reportResults { + reportNotes = append(reportNotes, reportResult.Notes...) + } + return reportNotes, nil +} + +// Parse parses an analysis report from a git note. +func Parse(note repository.Note) (Report, error) { + bytes := []byte(note) + var report Report + err := json.Unmarshal(bytes, &report) + return report, err +} + +// GetLatestAnalysesReport takes a collection of analysis reports, and returns the one with the most recent timestamp. +func GetLatestAnalysesReport(reports []Report) (*Report, error) { + timestampReportMap := make(map[int]*Report) + var timestamps []int + + for _, report := range reports { + timestamp, err := strconv.Atoi(report.Timestamp) + if err != nil { + return nil, err + } + timestamps = append(timestamps, timestamp) + timestampReportMap[timestamp] = &report + } + if len(timestamps) == 0 { + return nil, nil + } + sort.Sort(sort.Reverse(sort.IntSlice(timestamps))) + return timestampReportMap[timestamps[0]], nil +} + +// ParseAllValid takes collection of git notes and tries to parse a analyses report +// from each one. Any notes that are not valid analyses reports get ignored. +func ParseAllValid(notes []repository.Note) []Report { + var reports []Report + for _, note := range notes { + report, err := Parse(note) + if err == nil && report.Version == FormatVersion { + reports = append(reports, report) + } + } + return reports +} diff --git a/third_party/go/git-appraise/review/analyses/analyses_test.go b/third_party/go/git-appraise/review/analyses/analyses_test.go new file mode 100644 index 000000000000..00a811ef6a40 --- /dev/null +++ b/third_party/go/git-appraise/review/analyses/analyses_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package analyses + +import ( + "fmt" + "github.com/google/git-appraise/repository" + "net/http" + "net/http/httptest" + "testing" +) + +const ( + mockOldReport = `{"timestamp": "0", "url": "https://this-url-does-not-exist.test/analysis.json"}` + mockNewReport = `{"timestamp": "1", "url": "%s"}` + mockResults = `{ + "analyze_response": [{ + "note": [{ + "location": { + "path": "file.txt", + "range": { + "start_line": 5 + } + }, + "category": "test", + "description": "This is a test" + }] + }] +}` +) + +func mockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + t.Log(r) + fmt.Fprintln(w, mockResults) + w.WriteHeader(http.StatusOK) + } +} + +func TestGetLatestResult(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockHandler(t))) + defer mockServer.Close() + + reports := ParseAllValid([]repository.Note{ + repository.Note([]byte(mockOldReport)), + repository.Note([]byte(fmt.Sprintf(mockNewReport, mockServer.URL))), + }) + + report, err := GetLatestAnalysesReport(reports) + if err != nil { + t.Fatal("Unexpected error while parsing analysis reports", err) + } + if report == nil { + t.Fatal("Unexpected nil report") + } + reportResult, err := report.GetLintReportResult() + if err != nil { + t.Fatal("Unexpected error while reading the latest report's results", err) + } + if len(reportResult) != 1 { + t.Fatal("Unexpected report result", reportResult) + } +} |