Example #1
0
// callGetAnalyzerInfo requests the categories for the specified analyzer and puts them onto the
// channel provided. If anything goes wrong, it returns the empty set.
func callGetAnalyzerInfo(analyzer string, out chan<- serviceInfo) {
	httpClient := getHTTPClient(analyzer)
	var catResp rpcpb.GetCategoryResponse
	var stageResp rpcpb.GetStageResponse
	var cats strset.Set
	var stage contextpb.Stage
	// TODO(ciera): Maybe we should just combine these into one call...
	err := httpClient.Call("/AnalyzerService/GetCategory", &rpcpb.GetCategoryRequest{}, &catResp)
	if err != nil {
		log.Printf("Could not get categories from %s: %v", analyzer, err)
		cats = strset.New()
	} else {
		cats = strset.New(catResp.Category...)
	}

	err = httpClient.Call("/AnalyzerService/GetStage", &rpcpb.GetStageRequest{}, &stageResp)
	if err != nil {
		log.Printf("Could not get stage from %s: %v", analyzer, err)
		cats = strset.New()
	} else {
		stage = *stageResp.Stage
	}

	out <- serviceInfo{
		analyzer:   analyzer,
		categories: cats,
		stage:      stage,
	}
}
func TestGetServiceInfo(t *testing.T) {
	addr2, cleanup, err := testutil.CreatekRPCTestServer(&fakeDispatcher{[]string{"Foo", "Bar"}, nil}, "AnalyzerService")
	if err != nil {
		t.Fatalf("Registering analyzer service failed: %v", err)
	}
	defer cleanup()

	addr0, cleanup, err := testutil.CreatekRPCTestServer(&fakeDispatcher{nil, nil}, "AnalyzerService")
	if err != nil {
		t.Fatalf("Registering analyzer service failed: %v", err)
	}
	defer cleanup()

	addre, cleanup, err := testutil.CreatekRPCTestServer(&errDispatcher{}, "AnalyzerService")
	if err != nil {
		t.Fatalf("Registering analyzer service failed: %v", err)
	}
	defer cleanup()

	addrp, cleanup, err := testutil.CreatekRPCTestServer(&panicDispatcher{}, "AnalyzerService")
	if err != nil {
		t.Fatalf("Registering analyzer service failed: %v", err)
	}
	defer cleanup()

	tests := []struct {
		addrs  []string
		result map[string]strset.Set
	}{
		{[]string{addr0}, map[string]strset.Set{addr0: nil}},
		{[]string{addr2}, map[string]strset.Set{addr2: strset.New("Foo", "Bar")}},
		{[]string{addr0, addr2}, map[string]strset.Set{addr2: strset.New("Foo", "Bar"), addr0: nil}},
		{[]string{addrp, addr2}, map[string]strset.Set{addr2: strset.New("Foo", "Bar"), addrp: nil}},
		{[]string{addre, addr2}, map[string]strset.Set{addr2: strset.New("Foo", "Bar"), addre: nil}},
	}

	for _, test := range tests {
		driver := NewDriver(test.addrs, nil)
		info := driver.getAllServiceInfo()

		if len(test.result) != len(info) {
			t.Errorf("Incorrect number of results: got %v, want %v", info, test.result)
		}

		for addr, expectCats := range test.result {
			if !strset.Equal(info[strings.TrimPrefix(addr, "http://")].categories.ToSlice(), expectCats.ToSlice()) {
				t.Errorf("Incorrect categories for %s: got %v, want %v", addr, info[addr].categories, expectCats)
			}
		}
	}
}
func TestCallAllAnalyzers(t *testing.T) {
	dispatcher := &fakeDispatcher{categories: []string{"Foo", "Bar"}, files: []string{"dir1/A.h", "dir1/A.cc"}}
	addr, cleanup, err := testutil.CreatekRPCTestServer(dispatcher, "AnalyzerService")
	if err != nil {
		t.Fatalf("Registering analyzer service failed: %v", err)
	}
	defer cleanup()

	driver := NewTestDriver([]serviceInfo{
		serviceInfo{addr, strset.New("Foo", "Bar"), ctxpb.Stage_PRE_BUILD},
	})

	tests := []struct {
		files      []string
		categories []string
		expect     []*notepb.Note
	}{
		{
			// - SomeOtherCategory should be dropped; if it weren't fakeDispatcher would return an error.
			// - fakeDispatcher produces notes for files we didn't ask about; they should be dropped.
			[]string{"dir1/A.cc"},
			[]string{"Foo", "SomeOtherCategory"},
			[]*notepb.Note{
				&notepb.Note{
					Category:    proto.String("Foo"),
					Description: proto.String(""),
					Location:    testutil.CreateLocation("dir1/A.cc"),
				},
			},
		},
	}
	for _, test := range tests {
		ctx := &ctxpb.ShipshapeContext{FilePath: test.files}

		ars := driver.callAllAnalyzers(strset.New(test.categories...), ctx, ctxpb.Stage_PRE_BUILD)
		var notes []*notepb.Note

		for _, ar := range ars {
			notes = append(notes, ar.Note...)
			if len(ar.Failure) > 0 {
				t.Errorf("Received failures from analyze call: %v", ar.Failure)
			}
		}

		ok, results := testutil.CheckNoteContainsContent(test.expect, notes)
		if !ok {
			t.Errorf("Incorrect notes for categories %v: %s\n got %v, want %v", test.categories, results, notes, test.expect)
		}
	}
}
Example #4
0
// allCats returns the entire set of categories for the driver, across all analyzers
func (sd ShipshapeDriver) allCats() strset.Set {
	var catSet = strset.New()
	for _, info := range sd.serviceMap {
		catSet.AddSet(info.categories)
	}
	return catSet
}
Example #5
0
// Analyze will determine which analyzers to run and call them as appropriate. If necessary, it will
// also modify the context before calling the analyzers. It recovers from all analyzer panics with a
// note that the analyzer failed.
func (s analyzerService) Analyze(ctx server.Context, in *rpcpb.AnalyzeRequest) (resp *rpcpb.AnalyzeResponse, err error) {
	resp = new(rpcpb.AnalyzeResponse)

	log.Printf("called with: %v", proto.MarshalTextString(in))
	log.Print("starting analyzing")
	var nts []*notepb.Note
	var errs []*rpcpb.AnalysisFailure

	defer func() {
		resp.Note = nts
		resp.Failure = errs
	}()

	orgDir, restore, err := file.ChangeDir(*in.ShipshapeContext.RepoRoot)
	if err != nil {
		log.Printf("Internal error before analyzing: %v", err)
		appendFailure(&errs, "InternalFailure", err)
		return resp, err
	}
	defer func() {
		if err := restore(); err != nil {
			log.Printf("could not return back into %s from %s: %v", orgDir, *in.ShipshapeContext.RepoRoot, err)
		}
	}()

	reqCats := strset.New(in.Category...)
	for _, a := range s.analyzers {
		if reqCats.Contains(a.Category()) {
			runAnalyzer(a, in.ShipshapeContext, &nts, &errs)
		}
	}
	log.Printf("finished analyzing, sending back %d notes and %d errors", len(nts), len(errs))
	return resp, nil
}
func (f fakeDispatcher) Analyze(ctx server.Context, in *rpcpb.AnalyzeRequest) (*rpcpb.AnalyzeResponse, error) {
	var nts []*notepb.Note

	// Assert that the analyzer was called with the right categories.
	if !isSubset(strset.New(in.Category...), strset.New(f.categories...)) {
		return nil, fmt.Errorf("Category mismatch: got %v, supports %v", in.Category, f.categories)
	}

	for _, file := range f.files {
		nts = append(nts, &notepb.Note{
			Category:    proto.String(f.categories[0]),
			Description: proto.String("Hello world"),
			Location:    testutil.CreateLocation(file),
		})
	}

	return &rpcpb.AnalyzeResponse{
		Note: nts,
	}, nil
}
Example #7
0
// filterResults removes any notes where the category is nil, the category is not specified for
// the file path by the configuration, or there is no location with a source context.
// The config category and internal failure category cannot be turned off.
func filterResults(context *contextpb.ShipshapeContext, response *rpcpb.AnalyzeResponse) *rpcpb.AnalyzeResponse {
	files := strset.New(context.FilePath...)
	var keep []*notepb.Note
	for _, note := range response.Note {
		if note.Category != nil {
			if note.Location != nil && (note.Location.Path == nil || files.Contains(*note.Location.Path)) {
				keep = append(keep, note)
			}
		}
	}

	return &rpcpb.AnalyzeResponse{
		Note:    keep,
		Failure: response.Failure,
	}
}
Example #8
0
// Run runs the analyzers that this driver knows about on the provided ShipshapeRequest,
// taking configuration into account.
func (sd ShipshapeDriver) Run(ctx server.Context, in *rpcpb.ShipshapeRequest, out chan<- *rpcpb.ShipshapeResponse) error {
	var ars []*rpcpb.AnalyzeResponse
	log.Printf("Received analysis request for event %v, stage %v, categories %v, repo %v", *in.Event, *in.Stage, in.TriggeredCategory, *in.ShipshapeContext.RepoRoot)

	// However we exit, send back the set of collected AnalyzeResponses
	// TODO(ciera): we should be streaming back the responses, not sending them all at the end.
	defer func() {
		out <- &rpcpb.ShipshapeResponse{
			AnalyzeResponse: ars,
		}
	}()

	if in.ShipshapeContext.RepoRoot == nil {
		return fmt.Errorf("No repo root was set")
	}
	root := *in.ShipshapeContext.RepoRoot

	// cd into the root directory
	orgDir, restore, err := file.ChangeDir(root)
	if err != nil {
		log.Printf("Could not change into directory %s from base %s", root, orgDir)
		ars = append(ars, generateFailure("Driver setup", fmt.Sprint(err)))
		return err
	}
	defer func() {
		if err := restore(); err != nil {
			log.Printf("could not return back into %s from %s: %v", orgDir, root, err)
		}
	}()

	cfg, err := loadConfig(configFilename, *in.Event)
	if err != nil {
		log.Print("error loading config")
		// TODO(collinwinter): attach the error to the config file.
		ars = append(ars, generateFailure("Driver setup", err.Error()))
		return err
	}

	// Get the list of all categories
	sd.serviceMap = sd.getAllServiceInfo()
	allCats := sd.allCats()

	// Use the triggered categories if specified
	var desiredCats strset.Set
	if len(in.TriggeredCategory) > 0 {
		desiredCats = strset.New(in.TriggeredCategory...)
	} else if cfg != nil {
		desiredCats = strset.New(cfg.categories...)
		if len(desiredCats) > 0 {
			log.Printf("Running with categories from .shipshape file: %s" + desiredCats.String())
		} else if *in.Event != defaults.DefaultEvent {
			return fmt.Errorf("No categories configured for event %s", *in.Event)
		}
	}

	if len(desiredCats) == 0 {
		log.Printf("No categories specified, running with default categories: %s", sd.defaultCategories.String())
		desiredCats = sd.defaultCategories
	}

	// Find out what categories we have available, and remove/warn on the missing ones
	missingCats := strset.New().AddSet(desiredCats).RemoveSet(allCats)
	for missing := range missingCats {
		ars = append(ars, generateFailure(missing, fmt.Sprintf("The triggered category %q could not be found at the locations %v", missing, sd.AnalyzerLocations)))
	}
	desiredCats = desiredCats.RemoveSet(missingCats)

	if len(desiredCats) == 0 {
		log.Printf("No categories configured to run, doing nothing")
		return nil
	}

	// TODO(ciera): move this global ignore stuff into the CLI processing
	ignorePaths := []string{}
	if cfg != nil {
		ignorePaths = cfg.ignore
	}
	// Fill in the file_paths if they are empty in the context
	context := proto.Clone(in.ShipshapeContext).(*contextpb.ShipshapeContext)
	context.FilePath, err = retrieveAndFilterFiles(*context.RepoRoot, context.FilePath, ignorePaths)
	if err != nil {
		log.Printf("Had problems accessing files: %v", err.Error())
		ars = append(ars, generateFailure("Driver setup", fmt.Sprint(err)))
		return err
	}
	if len(context.FilePath) == 0 {
		log.Print("No files to run on, doing nothing")
		return nil
	}

	// TODO(ciera): rather than pass the stage through here and checking all analyzers,
	// filter out the stages earlier, when we check categories
	stage := contextpb.Stage_PRE_BUILD
	if in.Stage != nil {
		stage = *in.Stage
	}

	log.Printf("Analyzing stage %s", stage.String())
	if stage == contextpb.Stage_PRE_BUILD {
		ars = append(ars, sd.callAllAnalyzers(desiredCats, context, stage)...)
	} /*else {
		comps := filepath.Join(*context.RepoRoot, compilationsDir)
		compUnits, err := findCompilationUnits(comps)
		log.Printf("Found %d compUnits at %s", len(compUnits), comps)
		if err != nil {
			log.Printf("Could not retrieve compilation units: %v", err)
			ars = append(ars, generateFailure("Driver setup", err.Error()))
			return nil
		}
		for path, compUnit := range compUnits {
			context.CompilationDetails = &contextpb.CompilationDetails{
				CompilationUnit:            compUnit,
				CompilationDescriptionPath: proto.String(path),
			}
			log.Printf("Calling services with comp unit at %s", path)
			ars = append(ars, sd.callAllAnalyzers(desiredCats, context, stage)...)
		}

	}
	*/

	log.Print("Analysis completed")
	return nil
}
func TestCallAllAnalyzersErrorCases(t *testing.T) {
	ctx := &ctxpb.ShipshapeContext{FilePath: []string{"dir1/A", "dir2/B"}}

	tests := []struct {
		response      *rpcpb.AnalyzeResponse
		expectNotes   []*notepb.Note
		expectFailure []*rpcpb.AnalysisFailure
	}{
		{ //analysis had a failure
			&rpcpb.AnalyzeResponse{
				Failure: []*rpcpb.AnalysisFailure{
					&rpcpb.AnalysisFailure{
						Category:       proto.String("Foo"),
						FailureMessage: proto.String("badbadbad"),
					},
				},
			},
			nil,
			[]*rpcpb.AnalysisFailure{
				&rpcpb.AnalysisFailure{
					Category:       proto.String("Foo"),
					FailureMessage: proto.String("badbadbad"),
				},
			},
		},
		{ //analysis had both failure and notes
			&rpcpb.AnalyzeResponse{
				Note: []*notepb.Note{
					&notepb.Note{
						Category:    proto.String("Foo"),
						Description: proto.String("A note"),
						Location:    testutil.CreateLocation("dir1/A"),
					},
					&notepb.Note{
						Category:    proto.String("Foo"),
						Description: proto.String("A note"),
						Location:    testutil.CreateLocation("dir1/A"),
					},
				},
				Failure: []*rpcpb.AnalysisFailure{
					&rpcpb.AnalysisFailure{
						Category:       proto.String("Foo"),
						FailureMessage: proto.String("badbadbad"),
					},
				},
			},
			[]*notepb.Note{
				&notepb.Note{
					Category:    proto.String("Foo"),
					Description: proto.String("A note"),
					Location:    testutil.CreateLocation("dir1/A"),
				},
				&notepb.Note{
					Category:    proto.String("Foo"),
					Description: proto.String("A note"),
					Location:    testutil.CreateLocation("dir1/A"),
				},
			},
			[]*rpcpb.AnalysisFailure{
				&rpcpb.AnalysisFailure{
					Category:       proto.String("Foo"),
					FailureMessage: proto.String("badbadbad"),
				},
			},
		},
	}
	for _, test := range tests {
		addr, cleanup, err := testutil.CreatekRPCTestServer(&fullFakeDispatcher{test.response}, "AnalyzerService")
		if err != nil {
			t.Fatalf("Registering analyzer service failed: %v", err)
		}
		defer cleanup()

		driver := NewTestDriver([]serviceInfo{
			serviceInfo{addr, strset.New("Foo"), ctxpb.Stage_PRE_BUILD},
		})

		ars := driver.callAllAnalyzers(strset.New("Foo"), ctx, ctxpb.Stage_PRE_BUILD)
		var notes []*notepb.Note
		var failures []*rpcpb.AnalysisFailure

		for _, ar := range ars {
			notes = append(notes, ar.Note...)
			failures = append(failures, ar.Failure...)
		}

		ok, results := testutil.CheckNoteContainsContent(test.expectNotes, notes)
		if !ok {
			t.Errorf("Incorrect notes for original response %v: %s\n got %v, want %v", test.response, results, notes, test.expectNotes)
		}
		ok, results = testutil.CheckFailureContainsContent(test.expectFailure, failures)
		if !ok {
			t.Errorf("Incorrect failures for original response %v: %s\n got %v, want %v", test.response, results, failures, test.expectFailure)
		}

	}
}
Example #10
0
func main() {
	flag.Parse()

	log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile)
	analyzerList := strings.Split(*analyzers, ",")

	log.Printf("Waiting for analyzers to become healthy...")
	healthErrors := service.WaitForAnalyzers(analyzerList)
	allHealthy := true
	for addr, err := range healthErrors {
		if err != nil {
			log.Printf("Analyzer at %s failed to become healthy: %v", addr, err)
			allHealthy = false
		} else {
			log.Printf("Analyzer at %s registered", addr)
		}
	}
	if allHealthy {
		log.Printf("All analyzers deemed healthy")
	}

	defaultCategories := strset.New(
		"CheckstyleGoogle",
		"ErrorProne",
		"CodeAlert",
		"JSHint",
		"PyLint",
		"go vet")
	shipshapeService := service.NewDriver(analyzerList, defaultCategories)

	if *startService {
		// Start shipshape service
		s1 := server.Service{Name: serviceName}
		if err := s1.Register(shipshapeService); err != nil {
			log.Fatalf("Registering shipshape service failed: %v", err)
		}
		addr := fmt.Sprintf(":%d", *servicePort)
		log.Printf("Starting server endpoint at %q with service name %s\n", addr, serviceName)
		http.Handle("/", server.Endpoint{&s1})
		if err := http.ListenAndServe(addr, nil); err != nil {
			log.Fatalf("Server startup failed: %v", err)
		}
	} else {
		log.Println("Waiting for stdin. Specify --start_service if you meant to start as a service.")

		// Read request bytes from stdin
		requestBytes, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			log.Fatal("failed to read stdin: ", err)
		}

		log.Printf("Read shipshape request on stdin [%v bytes]", len(requestBytes))

		// Convert bytes from stdin to Shipshape request
		request := new(rpcpb.ShipshapeRequest)
		err = proto.Unmarshal(requestBytes, request)
		if err != nil {
			log.Fatal("failed to unmarshal shipshape request stream: ", err)
		}

		c := make(chan *rpcpb.ShipshapeResponse)

		go func() {
			if err := shipshapeService.Run(nil, request, c); err != nil {
				log.Printf("Failed to run on server: %v", err)
			}
		}()

		log.Print("Sent request to driver")

		response := <-c

		log.Printf("Shipshape response: [%s]", response)

		responseBytes, err := proto.Marshal(response)
		if err != nil {
			log.Fatal("failed to marshal shipshape response: ", err)
		}

		log.Printf("Writing Shipshape response to stdout")

		os.Stdout.Write(responseBytes)
	}
}