about summary refs log tree commit diff
path: root/third_party/go/git-appraise/review/analyses
diff options
context:
space:
mode:
authorVincent Ambo <tazjin@google.com>2019-07-02T13·19+0100
committerVincent Ambo <tazjin@google.com>2019-07-02T13·19+0100
commitfe642c30f01c4f3f6637851595ad1b36032461aa (patch)
treec0d0724f185add97673fb119122964dc95778f09 /third_party/go/git-appraise/review/analyses
parente03f0630523d708e144cf340bb00dfd957e167b6 (diff)
feat(third_party): Check in git-appraise r/10
Diffstat (limited to 'third_party/go/git-appraise/review/analyses')
-rw-r--r--third_party/go/git-appraise/review/analyses/analyses.go160
-rw-r--r--third_party/go/git-appraise/review/analyses/analyses_test.go77
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)
+	}
+}