github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/build/command_replacements_test.go (about)

     1  // Tests the command replacement functionality.
     2  
     3  package build
     4  
     5  import (
     6  	"io/ioutil"
     7  	"os"
     8  	"path"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  
    13  	"core"
    14  )
    15  
    16  var wd string
    17  var state *core.BuildState
    18  
    19  func init() {
    20  	state = core.NewDefaultBuildState()
    21  	wd, _ = os.Getwd()
    22  }
    23  
    24  func TestLocation(t *testing.T) {
    25  	target2 := makeTarget("//path/to:target2", "", nil)
    26  	target1 := makeTarget("//path/to:target1", "ln -s $(location //path/to:target2) ${OUT}", target2)
    27  
    28  	expected := "ln -s path/to/target2.py ${OUT}"
    29  	cmd := replaceSequences(state, target1)
    30  	if cmd != expected {
    31  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
    32  	}
    33  }
    34  
    35  func TestLocations(t *testing.T) {
    36  	target2 := makeTarget("//path/to:target2", "", nil)
    37  	target2.AddOutput("target2_other.py")
    38  	target1 := makeTarget("//path/to:target1", "cat $(locations //path/to:target2) > ${OUT}", target2)
    39  
    40  	expected := "cat path/to/target2.py path/to/target2_other.py > ${OUT}"
    41  	cmd := replaceSequences(state, target1)
    42  	if cmd != expected {
    43  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
    44  	}
    45  }
    46  
    47  func TestExe(t *testing.T) {
    48  	target2 := makeTarget("//path/to:target2", "", nil)
    49  	target2.IsBinary = true
    50  	target1 := makeTarget("//path/to:target1", "$(exe //path/to:target2) -o ${OUT}", target2)
    51  
    52  	expected := "path/to/target2.py -o ${OUT}"
    53  	cmd := replaceSequences(state, target1)
    54  	if cmd != expected {
    55  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
    56  	}
    57  }
    58  
    59  func TestOutExe(t *testing.T) {
    60  	target2 := makeTarget("//path/to:target2", "", nil)
    61  	target2.IsBinary = true
    62  	target1 := makeTarget("//path/to:target1", "$(out_exe //path/to:target2) -o ${OUT}", target2)
    63  
    64  	expected := "plz-out/bin/path/to/target2.py -o ${OUT}"
    65  	cmd := replaceSequences(state, target1)
    66  	if cmd != expected {
    67  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
    68  	}
    69  }
    70  
    71  func TestJavaExe(t *testing.T) {
    72  	target2 := makeTarget("//path/to:target2", "", nil)
    73  	target2.IsBinary = true
    74  	target2.AddLabel("java_non_exe") // This label tells us to prefix it with java -jar.
    75  	target1 := makeTarget("//path/to:target1", "$(exe //path/to:target2) -o ${OUT}", target2)
    76  
    77  	expected := "java -jar path/to/target2.py -o ${OUT}"
    78  	cmd := replaceSequences(state, target1)
    79  	if cmd != expected {
    80  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
    81  	}
    82  }
    83  
    84  func TestJavaOutExe(t *testing.T) {
    85  	target2 := makeTarget("//path/to:target2", "", nil)
    86  	target2.IsBinary = true
    87  	target2.AddLabel("java_non_exe") // This label tells us to prefix it with java -jar.
    88  	target1 := makeTarget("//path/to:target1", "$(out_exe //path/to:target2) -o ${OUT}", target2)
    89  
    90  	expected := "java -jar plz-out/bin/path/to/target2.py -o ${OUT}"
    91  	cmd := replaceSequences(state, target1)
    92  	if cmd != expected {
    93  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
    94  	}
    95  }
    96  
    97  func TestReplacementsForTest(t *testing.T) {
    98  	target2 := makeTarget("//path/to:target2", "", nil)
    99  	target1 := makeTarget("//path/to:target1", "$(exe //path/to:target1) $(location //path/to:target2)", target2)
   100  	target1.IsBinary = true
   101  	target1.IsTest = true
   102  
   103  	expected := "./target1.py path/to/target2.py"
   104  	cmd := ReplaceTestSequences(state, target1, target1.Command)
   105  	if cmd != expected {
   106  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
   107  	}
   108  }
   109  
   110  func TestDataReplacementForTest(t *testing.T) {
   111  	target := makeTarget("//path/to:target1", "cat $(location test_data.txt)", nil)
   112  	target.Data = append(target.Data, core.FileLabel{File: "test_data.txt", Package: "path/to"})
   113  
   114  	expected := "cat path/to/test_data.txt"
   115  	cmd := ReplaceTestSequences(state, target, target.Command)
   116  	if cmd != expected {
   117  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
   118  	}
   119  }
   120  
   121  func TestAmpersandReplacement(t *testing.T) {
   122  	target := makeTarget("//path/to:target1", "cat $(location b&b.txt)", nil)
   123  	expected := "cat \"path/to/b&b.txt\""
   124  	cmd := ReplaceSequences(state, target, target.Command)
   125  	if cmd != expected {
   126  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
   127  	}
   128  }
   129  
   130  func TestToolReplacement(t *testing.T) {
   131  	target2 := makeTarget("//path/to:target2", "blah", nil)
   132  	target1 := makeTarget("//path/to:target1", "$(location //path/to:target2)", target2)
   133  	target1.Tools = append(target1.Tools, target2.Label)
   134  
   135  	wd, _ := os.Getwd()
   136  	expected := quote(path.Join(wd, "plz-out/gen/path/to/target2.py"))
   137  	cmd := ReplaceSequences(state, target1, target1.Command)
   138  	if cmd != expected {
   139  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
   140  	}
   141  }
   142  
   143  func TestDirReplacement(t *testing.T) {
   144  	target2 := makeTarget("//path/to:target2", "blah", nil)
   145  	target2.AddOutput("blah2.txt")
   146  	target1 := makeTarget("//path/to:target1", "$(dir //path/to:target2)", target2)
   147  
   148  	expected := "path/to"
   149  	cmd := ReplaceSequences(state, target1, target1.Command)
   150  	if cmd != expected {
   151  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
   152  	}
   153  }
   154  
   155  func TestToolDirReplacement(t *testing.T) {
   156  	target2 := makeTarget("//path/to:target2", "blah", nil)
   157  	target2.AddOutput("blah2.txt")
   158  	target1 := makeTarget("//path/to:target1", "$(dir //path/to:target2)", target2)
   159  	target1.Tools = append(target1.Tools, target2.Label)
   160  
   161  	wd, _ := os.Getwd()
   162  	expected := quote(path.Join(wd, "plz-out/gen/path/to"))
   163  	cmd := ReplaceSequences(state, target1, target1.Command)
   164  	if cmd != expected {
   165  		t.Errorf("Replacement sequence not as expected; is %s, should be %s", cmd, expected)
   166  	}
   167  }
   168  
   169  func TestBazelCompatReplacements(t *testing.T) {
   170  	// Check that we don't do any of these things normally.
   171  	target := makeTarget("//path/to:target", "cp $< $@", nil)
   172  	assert.Equal(t, "cp $< $@", replaceSequences(state, target))
   173  	// In Bazel compat mode we do though.
   174  	state := core.NewBuildState(1, nil, 1, core.DefaultConfiguration())
   175  	state.Config.Bazel.Compatibility = true
   176  	assert.Equal(t, "cp $SRCS $OUTS", replaceSequences(state, target))
   177  	// @D is the output dir, for us it's the tmp dir.
   178  	target.Command = "cp $SRCS $@D"
   179  	assert.Equal(t, "cp $SRCS $TMP_DIR", replaceSequences(state, target))
   180  	// This parenthesised syntax seems to be allowed too.
   181  	target.Command = "cp $(<) $(@)"
   182  	assert.Equal(t, "cp $SRCS $OUTS", replaceSequences(state, target))
   183  }
   184  
   185  func TestHashReplacement(t *testing.T) {
   186  	// Need to write the file that will be used to calculate the hash.
   187  	err := os.MkdirAll("plz-out/gen/path/to", 0755)
   188  	assert.NoError(t, err)
   189  	err = ioutil.WriteFile("plz-out/gen/path/to/target2.py", []byte(`"""Test file for command_replacements_test"""`), 0644)
   190  	assert.NoError(t, err)
   191  
   192  	target2 := makeTarget("//path/to:target2", "cp $< $@", nil)
   193  	target := makeTarget("//path/to:target", "echo $(hash //path/to:target2)", target2)
   194  	assert.Panics(t, func() { replaceSequences(state, target) }, "Can't use $(hash ) on a non-stamped target")
   195  	target.Stamp = true
   196  	// Note that this hash is determined arbitrarily, it doesn't matter for this test
   197  	// precisely what its value is.
   198  	assert.Equal(t, "echo gB4sUwsLkB1ODYKUxYrKGlpdYUI", replaceSequences(state, target))
   199  }
   200  
   201  func TestWorkerReplacement(t *testing.T) {
   202  	tool := makeTarget("//path/to:target2", "", nil)
   203  	tool.IsBinary = true
   204  	target := makeTarget("//path/to:target", "$(worker //path/to:target2) --some_arg", tool)
   205  	target.Tools = append(target.Tools, tool.Label)
   206  	worker, remoteArgs, localCmd := workerCommandAndArgs(state, target)
   207  	assert.Equal(t, wd+"/plz-out/bin/path/to/target2.py", worker)
   208  	assert.Equal(t, "--some_arg", remoteArgs)
   209  	assert.Equal(t, "", localCmd)
   210  }
   211  
   212  func TestSystemWorkerReplacement(t *testing.T) {
   213  	target := makeTarget("//path/to:target", "$(worker /usr/bin/javac) --some_arg", nil)
   214  	target.Tools = append(target.Tools, core.SystemFileLabel{Path: "/usr/bin/javac"})
   215  	worker, remoteArgs, localCmd := workerCommandAndArgs(state, target)
   216  	assert.Equal(t, "/usr/bin/javac", worker)
   217  	assert.Equal(t, "--some_arg", remoteArgs)
   218  	assert.Equal(t, "", localCmd)
   219  }
   220  
   221  func TestLocalCommandWorker(t *testing.T) {
   222  	tool := makeTarget("//path/to:target2", "", nil)
   223  	tool.IsBinary = true
   224  	target := makeTarget("//path/to:target", "$(worker //path/to:target2) --some_arg && find . | xargs rm && echo hello", tool)
   225  	target.Tools = append(target.Tools, tool.Label)
   226  	worker, remoteArgs, localCmd := workerCommandAndArgs(state, target)
   227  	assert.Equal(t, wd+"/plz-out/bin/path/to/target2.py", worker)
   228  	assert.Equal(t, "--some_arg", remoteArgs)
   229  	assert.Equal(t, "find . | xargs rm && echo hello", localCmd)
   230  }
   231  
   232  func TestWorkerCommandAndArgsMustComeFirst(t *testing.T) {
   233  	tool := makeTarget("//path/to:target2", "", nil)
   234  	tool.IsBinary = true
   235  	target := makeTarget("//path/to:target", "something something $(worker javac)", tool)
   236  	target.Tools = append(target.Tools, tool.Label)
   237  	assert.Panics(t, func() { workerCommandAndArgs(state, target) })
   238  }
   239  
   240  func TestWorkerReplacementWithNoWorker(t *testing.T) {
   241  	target := makeTarget("//path/to:target", "echo hello", nil)
   242  	worker, remoteArgs, localCmd := workerCommandAndArgs(state, target)
   243  	assert.Equal(t, "", worker)
   244  	assert.Equal(t, "", remoteArgs)
   245  	assert.Equal(t, "echo hello", localCmd)
   246  }
   247  
   248  func makeTarget(name string, command string, dep *core.BuildTarget) *core.BuildTarget {
   249  	target := core.NewBuildTarget(core.ParseBuildLabel(name, ""))
   250  	target.Command = command
   251  	target.AddOutput(target.Label.Name + ".py")
   252  	if dep != nil {
   253  		target.AddDependency(dep.Label)
   254  		// This is a bit awkward but I don't want to add a public interface just for a test.
   255  		graph := core.NewGraph()
   256  		graph.AddTarget(target)
   257  		graph.AddTarget(dep)
   258  		graph.AddDependency(target.Label, dep.Label)
   259  	}
   260  	return target
   261  }
   262  
   263  func replaceSequences(state *core.BuildState, target *core.BuildTarget) string {
   264  	return ReplaceSequences(state, target, target.GetCommand(state))
   265  }