// Copyright 2017, Google LLC // // 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 vision import ( "fmt" "reflect" "testing" "github.com/golang/protobuf/proto" "golang.org/x/net/context" pb "google.golang.org/genproto/googleapis/cloud/vision/v1" "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) var batchResponse = &pb.BatchAnnotateImagesResponse{ Responses: []*pb.AnnotateImageResponse{{ FaceAnnotations: []*pb.FaceAnnotation{ {RollAngle: 1}, {RollAngle: 2}}, LandmarkAnnotations: []*pb.EntityAnnotation{{Mid: "landmark"}}, LogoAnnotations: []*pb.EntityAnnotation{{Mid: "logo"}}, LabelAnnotations: []*pb.EntityAnnotation{{Mid: "label"}}, TextAnnotations: []*pb.EntityAnnotation{{Mid: "text"}}, FullTextAnnotation: &pb.TextAnnotation{Text: "full"}, SafeSearchAnnotation: &pb.SafeSearchAnnotation{Spoof: pb.Likelihood_POSSIBLE}, ImagePropertiesAnnotation: &pb.ImageProperties{DominantColors: &pb.DominantColorsAnnotation{}}, CropHintsAnnotation: &pb.CropHintsAnnotation{CropHints: []*pb.CropHint{{Confidence: 0.5}}}, WebDetection: &pb.WebDetection{WebEntities: []*pb.WebDetection_WebEntity{{EntityId: "web"}}}, }}, } // Verify that all the "shortcut" methods use the underlying // BatchAnnotateImages RPC correctly. func TestClientMethods(t *testing.T) { ctx := context.Background() c, err := NewImageAnnotatorClient(ctx, clientOpt) if err != nil { t.Fatal(err) } mockImageAnnotator.resps = []proto.Message{batchResponse} img := &pb.Image{Source: &pb.ImageSource{ImageUri: "http://foo.jpg"}} ictx := &pb.ImageContext{LanguageHints: []string{"en", "fr"}} req := &pb.AnnotateImageRequest{ Image: img, ImageContext: ictx, Features: []*pb.Feature{ {Type: pb.Feature_LABEL_DETECTION, MaxResults: 3}, {Type: pb.Feature_FACE_DETECTION, MaxResults: 4}, }, } for i, test := range []struct { call func() (interface{}, error) wantFeatures []*pb.Feature wantRes interface{} }{ { func() (interface{}, error) { return c.AnnotateImage(ctx, req) }, req.Features, batchResponse.Responses[0], }, { func() (interface{}, error) { return c.DetectFaces(ctx, img, ictx, 2) }, []*pb.Feature{{Type: pb.Feature_FACE_DETECTION, MaxResults: 2}}, batchResponse.Responses[0].FaceAnnotations, }, { func() (interface{}, error) { return c.DetectLandmarks(ctx, img, ictx, 2) }, []*pb.Feature{{Type: pb.Feature_LANDMARK_DETECTION, MaxResults: 2}}, batchResponse.Responses[0].LandmarkAnnotations, }, { func() (interface{}, error) { return c.DetectLogos(ctx, img, ictx, 2) }, []*pb.Feature{{Type: pb.Feature_LOGO_DETECTION, MaxResults: 2}}, batchResponse.Responses[0].LogoAnnotations, }, { func() (interface{}, error) { return c.DetectLabels(ctx, img, ictx, 2) }, []*pb.Feature{{Type: pb.Feature_LABEL_DETECTION, MaxResults: 2}}, batchResponse.Responses[0].LabelAnnotations, }, { func() (interface{}, error) { return c.DetectTexts(ctx, img, ictx, 2) }, []*pb.Feature{{Type: pb.Feature_TEXT_DETECTION, MaxResults: 2}}, batchResponse.Responses[0].TextAnnotations, }, { func() (interface{}, error) { return c.DetectDocumentText(ctx, img, ictx) }, []*pb.Feature{{Type: pb.Feature_DOCUMENT_TEXT_DETECTION, MaxResults: 0}}, batchResponse.Responses[0].FullTextAnnotation, }, { func() (interface{}, error) { return c.DetectSafeSearch(ctx, img, ictx) }, []*pb.Feature{{Type: pb.Feature_SAFE_SEARCH_DETECTION, MaxResults: 0}}, batchResponse.Responses[0].SafeSearchAnnotation, }, { func() (interface{}, error) { return c.DetectImageProperties(ctx, img, ictx) }, []*pb.Feature{{Type: pb.Feature_IMAGE_PROPERTIES, MaxResults: 0}}, batchResponse.Responses[0].ImagePropertiesAnnotation, }, { func() (interface{}, error) { return c.DetectWeb(ctx, img, ictx) }, []*pb.Feature{{Type: pb.Feature_WEB_DETECTION, MaxResults: 0}}, batchResponse.Responses[0].WebDetection, }, { func() (interface{}, error) { return c.CropHints(ctx, img, ictx) }, []*pb.Feature{{Type: pb.Feature_CROP_HINTS, MaxResults: 0}}, batchResponse.Responses[0].CropHintsAnnotation, }, } { mockImageAnnotator.reqs = nil res, err := test.call() if err != nil { t.Fatal(err) } got := mockImageAnnotator.reqs[0] want := &pb.BatchAnnotateImagesRequest{ Requests: []*pb.AnnotateImageRequest{{ Image: img, ImageContext: ictx, Features: test.wantFeatures, }}, } if !testEqual(got, want) { t.Errorf("#%d:\ngot %v\nwant %v", i, got, want) } if got, want := res, test.wantRes; !testEqual(got, want) { t.Errorf("#%d:\ngot %v\nwant %v", i, got, want) } } } func testEqual(a, b interface{}) bool { if a == nil && b == nil { return true } if a == nil || b == nil { return false } t := reflect.TypeOf(a) if t != reflect.TypeOf(b) { return false } if am, ok := a.(proto.Message); ok { return proto.Equal(am, b.(proto.Message)) } if t.Kind() != reflect.Slice { panic(fmt.Sprintf("testEqual can only handle proto.Message and slices, got %s", t)) } va := reflect.ValueOf(a) vb := reflect.ValueOf(b) if va.Len() != vb.Len() { return false } for i := 0; i < va.Len(); i++ { if !testEqual(va.Index(i).Interface(), vb.Index(i).Interface()) { return false } } return true } func TestAnnotateOneError(t *testing.T) { ctx := context.Background() c, err := NewImageAnnotatorClient(ctx, clientOpt) if err != nil { t.Fatal(err) } mockImageAnnotator.resps = []proto.Message{ &pb.BatchAnnotateImagesResponse{ Responses: []*pb.AnnotateImageResponse{{ Error: &status.Status{Code: int32(codes.NotFound), Message: "not found"}, }}, }, } _, err = c.annotateOne(ctx, &pb.Image{Source: &pb.ImageSource{ImageUri: "http://foo.jpg"}}, nil, pb.Feature_LOGO_DETECTION, 1, nil) if c := grpc.Code(err); c != codes.NotFound { t.Errorf("got %v, want NotFound", c) } }