github.com/tiagovtristao/plz@v13.4.0+incompatible/src/build/build_step_test.go (about)

     1  // Tests around the main part of the build process.
     2  // These are somewhat fiddly because by its nature the code has many side effects.
     3  // We attempt to minimise some through mocking.
     4  //
     5  // Note that because the tests run in an indeterminate order and maybe in parallel
     6  // they all have to be careful to use distinct build targets.
     7  
     8  package build
     9  
    10  import (
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"os"
    15  	"path"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/stretchr/testify/assert"
    20  	"gopkg.in/op/go-logging.v1"
    21  
    22  	"github.com/thought-machine/please/src/core"
    23  	"github.com/thought-machine/please/src/fs"
    24  )
    25  
    26  var cache core.Cache
    27  
    28  func TestBuildTargetWithNoDeps(t *testing.T) {
    29  	state, target := newState("//package1:target1")
    30  	target.AddOutput("file1")
    31  	err := buildTarget(1, state, target)
    32  	assert.NoError(t, err)
    33  	assert.Equal(t, core.Built, target.State())
    34  }
    35  
    36  func TestFailedBuildTarget(t *testing.T) {
    37  	state, target := newState("//package1:target1a")
    38  	target.Command = "false"
    39  	err := buildTarget(1, state, target)
    40  	assert.Error(t, err)
    41  }
    42  
    43  func TestBuildTargetWhichNeedsRebuilding(t *testing.T) {
    44  	// The output file for this target already exists, but it should still get rebuilt
    45  	// because there's no rule hash file.
    46  	state, target := newState("//package1:target2")
    47  	target.AddOutput("file2")
    48  	err := buildTarget(1, state, target)
    49  	assert.NoError(t, err)
    50  	assert.Equal(t, core.Built, target.State())
    51  }
    52  
    53  func TestBuildTargetWhichDoesntNeedRebuilding(t *testing.T) {
    54  	// We write a rule hash for this target before building it, so we don't need to build again.
    55  	state, target := newState("//package1:target3")
    56  	target.AddOutput("file3")
    57  	assert.NoError(t, writeRuleHash(state, target))
    58  	err := buildTarget(1, state, target)
    59  	assert.NoError(t, err)
    60  	assert.Equal(t, core.Reused, target.State())
    61  }
    62  
    63  func TestModifiedBuildTargetStillNeedsRebuilding(t *testing.T) {
    64  	// Similar to above, but if we change the target such that the rule hash no longer matches,
    65  	// it should get rebuilt.
    66  	state, target := newState("//package1:target4")
    67  	target.AddOutput("file4")
    68  	assert.NoError(t, writeRuleHash(state, target))
    69  	target.Command = "echo 'wibble wibble wibble' > $OUT"
    70  	target.RuleHash = nil // Have to force a reset of this
    71  	err := buildTarget(1, state, target)
    72  	assert.NoError(t, err)
    73  	assert.Equal(t, core.Built, target.State())
    74  }
    75  
    76  func TestSymlinkedOutputs(t *testing.T) {
    77  	// Test behaviour when the output is a symlink.
    78  	state, target := newState("//package1:target5")
    79  	target.AddOutput("file5")
    80  	target.AddSource(core.FileLabel{File: "src5", Package: "package1"})
    81  	target.Command = "ln -s $SRC $OUT"
    82  	err := buildTarget(1, state, target)
    83  	assert.NoError(t, err)
    84  	assert.Equal(t, core.Built, target.State())
    85  }
    86  
    87  func TestPreBuildFunction(t *testing.T) {
    88  	// Test modifying a command in the pre-build function.
    89  	state, target := newState("//package1:target6")
    90  	target.AddOutput("file6")
    91  	target.Command = "" // Target now won't produce the needed output
    92  	target.PreBuildFunction = preBuildFunction(func(target *core.BuildTarget) error {
    93  		target.Command = "echo 'wibble wibble wibble' > $OUT"
    94  		return nil
    95  	})
    96  	err := buildTarget(1, state, target)
    97  	assert.NoError(t, err)
    98  	assert.Equal(t, core.Built, target.State())
    99  }
   100  
   101  func TestPostBuildFunction(t *testing.T) {
   102  	// Test modifying a command in the post-build function.
   103  	state, target := newState("//package1:target7")
   104  	target.Command = "echo 'wibble wibble wibble' | tee file7"
   105  	target.PostBuildFunction = postBuildFunction(func(target *core.BuildTarget, output string) error {
   106  		target.AddOutput("file7")
   107  		assert.Equal(t, "wibble wibble wibble", output)
   108  		return nil
   109  	})
   110  	err := buildTarget(1, state, target)
   111  	assert.NoError(t, err)
   112  	assert.Equal(t, core.Built, target.State())
   113  	assert.Equal(t, []string{"file7"}, target.Outputs())
   114  }
   115  
   116  func TestCacheRetrieval(t *testing.T) {
   117  	// Test retrieving stuff from the cache
   118  	state, target := newState("//package1:target8")
   119  	target.AddOutput("file8")
   120  	target.Command = "false" // Will fail if we try to build it.
   121  	state.Cache = cache
   122  	err := buildTarget(1, state, target)
   123  	assert.NoError(t, err)
   124  	assert.Equal(t, core.Cached, target.State())
   125  }
   126  
   127  func TestPostBuildFunctionAndCache(t *testing.T) {
   128  	// Test the often subtle and quick to anger interaction of post-build function and cache.
   129  	// In this case when it fails to retrieve the post-build output it should still call the function after building.
   130  	state, target := newState("//package1:target9")
   131  	target.AddOutput("file9")
   132  	target.Command = "echo 'wibble wibble wibble' | tee $OUT"
   133  	called := false
   134  	target.PostBuildFunction = postBuildFunction(func(target *core.BuildTarget, output string) error {
   135  		called = true
   136  		assert.Equal(t, "wibble wibble wibble", output)
   137  		return nil
   138  	})
   139  	state.Cache = cache
   140  	err := buildTarget(1, state, target)
   141  	assert.NoError(t, err)
   142  	assert.Equal(t, core.Built, target.State())
   143  	assert.True(t, called)
   144  }
   145  
   146  func TestPostBuildFunctionAndCache2(t *testing.T) {
   147  	// Test the often subtle and quick to anger interaction of post-build function and cache.
   148  	// In this case it succeeds in retrieving the post-build output but must still call the function.
   149  	state, target := newState("//package1:target10")
   150  	target.AddOutput("file10")
   151  	target.Command = "echo 'wibble wibble wibble' | tee $OUT"
   152  	called := false
   153  	target.PostBuildFunction = postBuildFunction(func(target *core.BuildTarget, output string) error {
   154  		assert.False(t, called, "Must only call post-build function once (issue #113)")
   155  		called = true
   156  		assert.Equal(t, "retrieved from cache", output) // comes from implementation below
   157  		return nil
   158  	})
   159  	state.Cache = cache
   160  	err := buildTarget(1, state, target)
   161  	assert.NoError(t, err)
   162  	assert.Equal(t, core.Cached, target.State())
   163  	assert.True(t, called)
   164  }
   165  
   166  func TestInitPyCreation(t *testing.T) {
   167  	state, _ := newState("//pypkg:wevs")
   168  	target1 := newPyFilegroup(state, "//pypkg:target1", "file1.py")
   169  	target2 := newPyFilegroup(state, "//pypkg:target2", "__init__.py")
   170  	assert.NoError(t, buildFilegroup(state, target1))
   171  	assert.True(t, fs.FileExists("plz-out/gen/pypkg/__init__.py"))
   172  	assert.NoError(t, buildFilegroup(state, target2))
   173  	d, err := ioutil.ReadFile("plz-out/gen/pypkg/__init__.py")
   174  	assert.NoError(t, err)
   175  	assert.Equal(t, `"""output from //pypkg:target2"""`, strings.TrimSpace(string(d)))
   176  }
   177  
   178  func TestRecursiveInitPyCreation(t *testing.T) {
   179  	state, _ := newState("//package1/package2:wevs")
   180  	target1 := newPyFilegroup(state, "//package1/package2:target1", "file1.py")
   181  	assert.NoError(t, buildFilegroup(state, target1))
   182  	assert.True(t, fs.FileExists("plz-out/gen/package1/package2/__init__.py"))
   183  	assert.True(t, fs.FileExists("plz-out/gen/package1/__init__.py"))
   184  }
   185  
   186  func TestCreatePlzOutGo(t *testing.T) {
   187  	state, target := newState("//gopkg:target")
   188  	target.AddLabel("link:plz-out/go/${PKG}/src")
   189  	target.AddOutput("file1.go")
   190  	assert.False(t, fs.PathExists("plz-out/go"))
   191  	assert.NoError(t, buildTarget(1, state, target))
   192  	assert.True(t, fs.PathExists("plz-out/go/gopkg/src/file1.go"))
   193  }
   194  
   195  func TestLicenceEnforcement(t *testing.T) {
   196  	state, target := newState("//pkg:good")
   197  	state.Config.Licences.Reject = append(state.Config.Licences.Reject, "gpl")
   198  	state.Config.Licences.Accept = append(state.Config.Licences.Accept, "mit")
   199  
   200  	// Target specifying no licence should not panic.
   201  	checkLicences(state, target)
   202  
   203  	// A license (non case sensitive) that is not in the list of accepted licenses will panic.
   204  	assert.Panics(t, func() {
   205  		target.Licences = append(target.Licences, "Bsd")
   206  		checkLicences(state, target)
   207  	}, "A target with a non-accepted licence will panic")
   208  
   209  	// Accepting bsd should resolve the panic
   210  	state.Config.Licences.Accept = append(state.Config.Licences.Accept, "BSD")
   211  	checkLicences(state, target)
   212  
   213  	// Now construct a new "bad" target.
   214  	state, target = newState("//pkg:bad")
   215  	state.Config.Licences.Reject = append(state.Config.Licences.Reject, "gpl")
   216  	state.Config.Licences.Accept = append(state.Config.Licences.Accept, "mit")
   217  
   218  	// Adding an explicitly rejected licence should panic no matter what.
   219  	target.Licences = append(target.Licences, "GPL")
   220  	assert.Panics(t, func() {
   221  		checkLicences(state, target)
   222  	}, "Trying to add GPL should panic (case insensitive)")
   223  }
   224  
   225  func TestFileGroupBinDir(t *testing.T) {
   226  	state, target := newState("//package1:bindir")
   227  	//target.AddOutput("test_data")
   228  	target.AddSource(core.FileLabel{File: "package2", Package: target.Label.PackageName})
   229  	target.IsBinary = true
   230  	target.IsFilegroup = true
   231  
   232  	err := buildFilegroup(state, target)
   233  	assert.NoError(t, err)
   234  
   235  	assert.True(t, fs.PathExists("plz-out/bin/package1/package2/"))
   236  	assert.True(t, fs.FileExists("plz-out/bin/package1/package2/file1.py"))
   237  	assert.True(t, fs.IsDirectory("plz-out/bin/package1/package2/"))
   238  
   239  	// Ensure correct permission on directory
   240  	info, err := os.Stat("plz-out/bin/package1/package2/")
   241  	assert.NoError(t, err)
   242  	assert.Equal(t, os.FileMode(0755), info.Mode().Perm())
   243  }
   244  
   245  func newState(label string) (*core.BuildState, *core.BuildTarget) {
   246  	config, _ := core.ReadConfigFiles(nil, "")
   247  	state := core.NewBuildState(1, nil, 4, config)
   248  	target := core.NewBuildTarget(core.ParseBuildLabel(label, ""))
   249  	target.Command = fmt.Sprintf("echo 'output of %s' > $OUT", target.Label)
   250  	state.Graph.AddTarget(target)
   251  	state.Parser = &fakeParser{}
   252  	return state, target
   253  }
   254  
   255  func newPyFilegroup(state *core.BuildState, label, filename string) *core.BuildTarget {
   256  	target := core.NewBuildTarget(core.ParseBuildLabel(label, ""))
   257  	target.AddSource(core.FileLabel{File: filename, Package: target.Label.PackageName})
   258  	target.AddOutput(filename)
   259  	target.AddLabel("py")
   260  	target.IsFilegroup = true
   261  	state.Graph.AddTarget(target)
   262  	return target
   263  }
   264  
   265  // Fake cache implementation with hardcoded behaviour for the various tests above.
   266  type mockCache struct{}
   267  
   268  func (*mockCache) Store(target *core.BuildTarget, key []byte, files ...string) {
   269  }
   270  
   271  func (*mockCache) StoreExtra(target *core.BuildTarget, key []byte, file string) {
   272  }
   273  
   274  func (*mockCache) Retrieve(target *core.BuildTarget, key []byte) bool {
   275  	if target.Label.Name == "target8" {
   276  		ioutil.WriteFile("plz-out/gen/package1/file8", []byte("retrieved from cache"), 0664)
   277  		return true
   278  	} else if target.Label.Name == "target10" {
   279  		ioutil.WriteFile("plz-out/gen/package1/file10", []byte("retrieved from cache"), 0664)
   280  		return true
   281  	}
   282  	return false
   283  }
   284  
   285  func (*mockCache) RetrieveExtra(target *core.BuildTarget, key []byte, file string) bool {
   286  	if target.Label.Name == "target10" && file == target.PostBuildOutputFileName() {
   287  		ioutil.WriteFile(postBuildOutputFileName(target), []byte("retrieved from cache"), 0664)
   288  		return true
   289  	}
   290  	return false
   291  }
   292  
   293  func (*mockCache) Clean(target *core.BuildTarget) {}
   294  func (*mockCache) CleanAll()                      {}
   295  func (*mockCache) Shutdown()                      {}
   296  
   297  type fakeParser struct {
   298  }
   299  
   300  func (fake *fakeParser) ParseFile(state *core.BuildState, pkg *core.Package, filename string) error {
   301  	return nil
   302  }
   303  
   304  func (fake *fakeParser) ParseReader(state *core.BuildState, pkg *core.Package, r io.ReadSeeker) error {
   305  	return nil
   306  }
   307  
   308  func (fake *fakeParser) RunPreBuildFunction(threadID int, state *core.BuildState, target *core.BuildTarget) error {
   309  	return target.PreBuildFunction.Call(target)
   310  }
   311  
   312  func (fake *fakeParser) RunPostBuildFunction(threadID int, state *core.BuildState, target *core.BuildTarget, output string) error {
   313  	return target.PostBuildFunction.Call(target, output)
   314  }
   315  
   316  type preBuildFunction func(*core.BuildTarget) error
   317  type postBuildFunction func(*core.BuildTarget, string) error
   318  
   319  func (f preBuildFunction) Call(target *core.BuildTarget) error { return f(target) }
   320  func (f preBuildFunction) String() string                      { return "" }
   321  func (f postBuildFunction) Call(target *core.BuildTarget, output string) error {
   322  	return f(target, output)
   323  }
   324  func (f postBuildFunction) String() string { return "" }
   325  
   326  func TestMain(m *testing.M) {
   327  	cache = &mockCache{}
   328  	backend := logging.NewLogBackend(os.Stderr, "", 0)
   329  	backendLeveled := logging.AddModuleLevel(backend)
   330  	backendLeveled.SetLevel(logging.DEBUG, "")
   331  	logging.SetBackend(backend, backendLeveled)
   332  	// Move ourselves to the root of the test data tree
   333  	wd, _ := os.Getwd()
   334  	core.RepoRoot = path.Join(wd, "src/build/test_data")
   335  	Init(nil)
   336  	if err := os.Chdir(core.RepoRoot); err != nil {
   337  		panic(err)
   338  	}
   339  	os.Exit(m.Run())
   340  }