kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/platform/analysis/driver/driver_test.go (about) 1 /* 2 * Copyright 2015 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package driver 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "strings" 25 "testing" 26 "time" 27 28 "kythe.io/kythe/go/platform/analysis" 29 "kythe.io/kythe/go/test/testutil" 30 "kythe.io/kythe/go/util/log" 31 32 apb "kythe.io/kythe/proto/analysis_go_proto" 33 spb "kythe.io/kythe/proto/storage_go_proto" 34 ) 35 36 type mock struct { 37 t *testing.T 38 39 idx int 40 41 Compilations []Compilation 42 Outputs []*apb.AnalysisOutput 43 AnalysisResult *apb.AnalysisResult 44 AnalyzeError error 45 OutputError error 46 47 AnalysisDuration time.Duration 48 49 OutputIndex int 50 Requests []*apb.AnalysisRequest 51 } 52 53 func (m *mock) out() analysis.OutputFunc { 54 return func(_ context.Context, out *apb.AnalysisOutput) error { 55 if m.OutputIndex >= len(m.Outputs) { 56 m.t.Fatal("OutputFunc called more times than expected") 57 } 58 expected := m.Outputs[m.OutputIndex] 59 if !bytes.Equal(out.Value, expected.Value) { 60 m.t.Errorf("Expected output %d: %q; found: %q", m.OutputIndex, string(expected.Value), string(out.Value)) 61 } 62 m.OutputIndex++ 63 return m.OutputError 64 } 65 } 66 67 const buildID = "aabbcc" 68 69 // Analyze implements the analysis.CompilationAnalyzer interface. 70 func (m *mock) Analyze(ctx context.Context, req *apb.AnalysisRequest, out analysis.OutputFunc) (*apb.AnalysisResult, error) { 71 if m.AnalysisDuration != 0 { 72 log.InfoContextf(ctx, "Waiting %s for analysis request", m.AnalysisDuration) 73 time.Sleep(m.AnalysisDuration) 74 } 75 m.OutputIndex = 0 76 m.Requests = append(m.Requests, req) 77 for _, o := range m.Outputs { 78 if err := out(ctx, o); err != m.OutputError { 79 m.t.Errorf("Expected OutputFunc error: %v; found: %v", m.OutputError, err) 80 return nil, err 81 } 82 } 83 if m.OutputIndex != len(m.Outputs) { 84 m.t.Errorf("Expected OutputIndex: %d; found: %d", len(m.Outputs), m.OutputIndex) 85 } 86 if req.BuildId != buildID { 87 m.t.Errorf("Missing build ID") 88 } 89 if err := ctx.Err(); err != nil { 90 return m.AnalysisResult, err 91 } 92 return m.AnalysisResult, m.AnalyzeError 93 } 94 95 // Next implements the Queue interface. 96 func (m *mock) Next(ctx context.Context, f CompilationFunc) error { 97 if m.idx >= len(m.Compilations) { 98 return ErrEndOfQueue 99 } 100 err := f(ctx, m.Compilations[m.idx]) 101 m.idx++ 102 return err 103 } 104 105 // A testContext implements the Context interface through local functions. 106 // The default implementations are no-ops without error. 107 type testContext struct { 108 setup func(context.Context, Compilation) error 109 teardown func(context.Context, Compilation) error 110 analysisError func(context.Context, Compilation, error) error 111 } 112 113 func (t testContext) Setup(ctx context.Context, unit Compilation) error { 114 if t.setup != nil { 115 return t.setup(ctx, unit) 116 } 117 return nil 118 } 119 120 func (t testContext) Teardown(ctx context.Context, unit Compilation) error { 121 if t.teardown != nil { 122 return t.teardown(ctx, unit) 123 } 124 return nil 125 } 126 127 func (t testContext) AnalysisError(ctx context.Context, unit Compilation, err error) error { 128 if t.analysisError != nil { 129 return t.analysisError(ctx, unit, err) 130 } 131 return err 132 } 133 134 func TestDriverInvalid(t *testing.T) { 135 m := &mock{t: t} 136 test := new(Driver) 137 if err := test.Run(context.Background(), m); err == nil { 138 t.Errorf("Expected error from %#v.Run but got none", test) 139 } 140 } 141 142 func TestDriverEmpty(t *testing.T) { 143 m := &mock{t: t} 144 d := &Driver{ 145 Analyzer: m, 146 WriteOutput: m.out(), 147 } 148 testutil.Fatalf(t, "Driver error: %v", d.Run(context.Background(), m)) 149 if len(m.Requests) != 0 { 150 t.Fatalf("Unexpected AnalysisRequests: %v", m.Requests) 151 } 152 } 153 154 func TestDriver(t *testing.T) { 155 m := &mock{ 156 t: t, 157 Outputs: outs("a", "b", "c"), 158 Compilations: comps("target1", "target2"), 159 } 160 var setupIdx, teardownIdx int 161 d := &Driver{ 162 Analyzer: m, 163 WriteOutput: m.out(), 164 Context: testContext{ 165 setup: func(_ context.Context, cu Compilation) error { 166 setupIdx++ 167 return nil 168 }, 169 teardown: func(_ context.Context, cu Compilation) error { 170 if setupIdx != teardownIdx+1 { 171 t.Error("Teardown was not called directly after Setup/Analyze") 172 } 173 teardownIdx++ 174 return nil 175 }, 176 analysisError: func(_ context.Context, _ Compilation, err error) error { 177 // Compilations that do not report an error should not call this hook. 178 t.Errorf("Unexpected call of AnalysisError hook with %v", err) 179 return err 180 }, 181 }, 182 } 183 testutil.Fatalf(t, "Driver error: %v", d.Run(context.Background(), m)) 184 if len(m.Requests) != len(m.Compilations) { 185 t.Errorf("Expected %d AnalysisRequests; found %v", len(m.Compilations), m.Requests) 186 } 187 if setupIdx != len(m.Compilations) { 188 t.Errorf("Expected %d calls to Setup; found %d", len(m.Compilations), setupIdx) 189 } 190 if teardownIdx != len(m.Compilations) { 191 t.Errorf("Expected %d calls to Teardown; found %d", len(m.Compilations), teardownIdx) 192 } 193 } 194 195 var errFromAnalysis = errors.New("some random analysis error") 196 197 func TestDriverAnalyzeError(t *testing.T) { 198 m := &mock{ 199 t: t, 200 Outputs: outs("a", "b", "c"), 201 Compilations: comps("target1", "target2"), 202 AnalyzeError: errFromAnalysis, 203 } 204 d := &Driver{ 205 Analyzer: m, 206 WriteOutput: m.out(), 207 } 208 if err := d.Run(context.Background(), m); err != errFromAnalysis { 209 t.Errorf("Expected AnalysisError: %v; found: %v", errFromAnalysis, err) 210 } 211 if len(m.Requests) != 1 { // we didn't analyze the second 212 t.Errorf("Expected %d AnalysisRequests; found %v", 1, m.Requests) 213 } 214 } 215 216 func TestDriverErrorHandler(t *testing.T) { 217 m := &mock{ 218 t: t, 219 Outputs: outs("a", "b", "c"), 220 Compilations: comps("target1", "target2"), 221 AnalyzeError: errFromAnalysis, 222 } 223 var analysisErr error 224 d := &Driver{ 225 Analyzer: m, 226 WriteOutput: m.out(), 227 Context: testContext{ 228 analysisError: func(_ context.Context, cu Compilation, err error) error { 229 analysisErr = err 230 return nil // don't return err 231 }, 232 }, 233 } 234 testutil.Fatalf(t, "Driver error: %v", d.Run(context.Background(), m)) 235 if len(m.Requests) != len(m.Compilations) { 236 t.Errorf("Expected %d AnalysisRequests; found %v", len(m.Compilations), m.Requests) 237 } 238 if analysisErr != errFromAnalysis { 239 t.Errorf("Expected AnalysisError: %v; found: %v", errFromAnalysis, analysisErr) 240 } 241 } 242 243 func TestDriverSetup(t *testing.T) { 244 m := &mock{ 245 t: t, 246 Outputs: outs("a", "b", "c"), 247 Compilations: comps("target1", "target2"), 248 } 249 var setupIdx int 250 d := &Driver{ 251 Analyzer: m, 252 WriteOutput: m.out(), 253 Context: testContext{ 254 setup: func(_ context.Context, cu Compilation) error { 255 setupIdx++ 256 var missing []string 257 if cu.UnitDigest == "" { 258 missing = append(missing, "unit digest") 259 } 260 if cu.Revision == "" { 261 missing = append(missing, "revision marker") 262 } 263 if cu.BuildID == "" { 264 missing = append(missing, "build ID") 265 } 266 if len(missing) != 0 { 267 return fmt.Errorf("missing %s: %v", strings.Join(missing, ", "), cu) 268 } 269 return nil 270 }, 271 }, 272 } 273 testutil.Fatalf(t, "Driver error: %v", d.Run(context.Background(), m)) 274 if len(m.Requests) != len(m.Compilations) { 275 t.Errorf("Expected %d AnalysisRequests; found %v", len(m.Compilations), m.Requests) 276 } 277 if setupIdx != len(m.Compilations) { 278 t.Errorf("Expected %d calls to Setup; found %d", len(m.Compilations), setupIdx) 279 } 280 } 281 282 func TestDriverTeardown(t *testing.T) { 283 m := &mock{ 284 t: t, 285 Outputs: outs("a", "b", "c"), 286 Compilations: comps("target1", "target2"), 287 } 288 var teardownIdx int 289 d := &Driver{ 290 Analyzer: m, 291 WriteOutput: m.out(), 292 Context: testContext{ 293 teardown: func(_ context.Context, cu Compilation) error { 294 teardownIdx++ 295 return nil 296 }, 297 }, 298 } 299 testutil.Fatalf(t, "Driver error: %v", d.Run(context.Background(), m)) 300 if len(m.Requests) != len(m.Compilations) { 301 t.Errorf("Expected %d AnalysisRequests; found %v", len(m.Compilations), m.Requests) 302 } 303 if teardownIdx != len(m.Compilations) { 304 t.Errorf("Expected %d calls to Teardown; found %d", len(m.Compilations), teardownIdx) 305 } 306 } 307 308 func TestDriverTimeout(t *testing.T) { 309 timeout := 10 * time.Millisecond 310 m := &mock{ 311 t: t, 312 Outputs: outs("a", "b", "c"), 313 Compilations: comps("target1", "target2"), 314 315 AnalysisDuration: timeout * 3, 316 } 317 d := &Driver{ 318 Analyzer: m, 319 AnalysisOptions: AnalysisOptions{Timeout: timeout}, 320 WriteOutput: m.out(), 321 Context: testContext{}, 322 } 323 if err := d.Run(context.Background(), m); !errors.Is(err, context.DeadlineExceeded) { 324 t.Errorf("Expected error %v; found %v", context.DeadlineExceeded, err) 325 } 326 if len(m.Requests) != 1 { 327 t.Errorf("Expected 1 AnalysisRequest; found %v", m.Requests) 328 } 329 } 330 331 func outs(vals ...string) (as []*apb.AnalysisOutput) { 332 for _, val := range vals { 333 as = append(as, &apb.AnalysisOutput{Value: []byte(val)}) 334 } 335 return 336 } 337 338 func comps(sigs ...string) (cs []Compilation) { 339 for _, sig := range sigs { 340 cs = append(cs, Compilation{ 341 Unit: &apb.CompilationUnit{VName: &spb.VName{Signature: sig}}, 342 Revision: "12345", 343 UnitDigest: "digest:" + sig, 344 BuildID: buildID, 345 }) 346 } 347 return 348 }