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  }