Repositories

Added review/analyses/analyses.go

@@ -0,0 +1,129 @@
/*
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"
"github.com/google/git-appraise/repository"
"io/ioutil"
"net/http"
"sort"
"strconv"
)
const (
// Ref defines the git-notes ref that we expect to contain analysis reports.
Ref = "refs/notes/devtools/analyses"
// 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"`
// Version represents the version of the metadata format.
Version int `json:"v,omitempty"`
}
type LocationRange struct {
StartLine int `json:"start_line,omitempty"`
}
type Location struct {
Path string `json:"path,omitempty"`
Range *LocationRange `json:"range,omitempty"`
}
type Note struct {
Location *Location `json:"location,omitempty"`
Category string `json:"category,omitempty"`
Description string `json:"description"`
}
type AnalyzeResponse struct {
Notes []Note `json:"note,omitempty"`
}
type ReportDetails struct {
AnalyzeResponse []AnalyzeResponse `json:"analyze_response,omitempty"`
}
func (lintReport Report) GetLintReportResult() ([]AnalyzeResponse, error) {
if lintReport.URL == "" {
return nil, nil
}
res, err := http.Get(lintReport.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
}
// 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
}

Added 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)
}
}

Modified review/ci/ci.go

@@ -19,8 +19,9 @@ package ci
import (
"encoding/json"
"github.com/google/git-appraise/repository"
"sort"
"strconv"
)
const (
@@ -56,6 +57,26 @@ func Parse(note repository.Note) (Report, error) {
return report, err
}
// GetLatestCIReport takes the collection of reports and returns the one with the most recent timestamp.
func GetLatestCIReport(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 CI report
// from each one. Any notes that are not valid CI reports get ignored, as we
// expect the git notes to be a heterogenous list, with only some of them

Added review/ci/ci_test.go

@@ -0,0 +1,85 @@
/*
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 ci
import (
"github.com/google/git-appraise/repository"
"testing"
)
const testCINote1 = `{
"Timestamp": "4",
"URL": "www.google.com",
"Status": "success"
}`
const testCINote2 = `{
"Timestamp": "16",
"URL": "www.google.com",
"Status": "failure"
}`
const testCINote3 = `{
"Timestamp": "30",
"URL": "www.google.com",
"Status": "something else"
}`
const testCINote4 = `{
"Timestamp": "28",
"URL": "www.google.com",
"Status": "success"
}`
const testCINote5 = `{
"Timestamp": "27",
"URL": "www.google.com",
"Status": "success"
}`
func TestCIReport(t *testing.T) {
latestReport, err := GetLatestCIReport(ParseAllValid([]repository.Note{
repository.Note(testCINote1),
repository.Note(testCINote2),
}))
if err != nil {
t.Fatal("Failed to properly fetch the latest report", err)
}
expected, err := Parse(repository.Note(testCINote2))
if err != nil {
t.Fatal("Failed to parse the expected report", err)
}
if *latestReport != expected {
t.Fatal("This is not the latest ", latestReport)
}
latestReport, err = GetLatestCIReport(ParseAllValid([]repository.Note{
repository.Note(testCINote1),
repository.Note(testCINote2),
repository.Note(testCINote3),
repository.Note(testCINote4),
}))
if err != nil {
t.Fatal("Failed to properly fetch the latest report: %v", err)
}
expected, err = Parse(repository.Note(testCINote4))
if err != nil {
t.Fatal("Failed to parse the expected report", err)
}
if *latestReport != expected {
t.Fatal("This is not the latest ", latestReport)
}
}