summaryrefslogtreecommitdiffstats
path: root/build/pymake/tests
diff options
context:
space:
mode:
Diffstat (limited to 'build/pymake/tests')
-rw-r--r--build/pymake/tests/automatic-variables.mk79
-rw-r--r--build/pymake/tests/bad-command-continuation.mk3
-rw-r--r--build/pymake/tests/call.mk12
-rw-r--r--build/pymake/tests/cmd-stripdotslash.mk5
-rw-r--r--build/pymake/tests/cmdgoals.mk9
-rw-r--r--build/pymake/tests/commandmodifiers.mk21
-rw-r--r--build/pymake/tests/comment-parsing.mk29
-rw-r--r--build/pymake/tests/continuations-in-functions.mk6
-rw-r--r--build/pymake/tests/datatests.py237
-rw-r--r--build/pymake/tests/default-goal-set-first.mk7
-rw-r--r--build/pymake/tests/default-goal.mk8
-rw-r--r--build/pymake/tests/default-target.mk14
-rw-r--r--build/pymake/tests/default-target2.mk6
-rw-r--r--build/pymake/tests/define-directive.mk69
-rw-r--r--build/pymake/tests/depfailed.mk4
-rw-r--r--build/pymake/tests/depfailedj.mk10
-rw-r--r--build/pymake/tests/diamond-deps.mk13
-rw-r--r--build/pymake/tests/dotslash-dir.mk8
-rw-r--r--build/pymake/tests/dotslash-parse.mk4
-rw-r--r--build/pymake/tests/dotslash-phony.mk3
-rw-r--r--build/pymake/tests/dotslash.mk9
-rw-r--r--build/pymake/tests/doublecolon-exists.mk16
-rw-r--r--build/pymake/tests/doublecolon-priordeps.mk19
-rw-r--r--build/pymake/tests/doublecolon-remake.mk4
-rw-r--r--build/pymake/tests/dynamic-var.mk18
-rw-r--r--build/pymake/tests/empty-arg.mk2
-rw-r--r--build/pymake/tests/empty-command-semicolon.mk5
-rw-r--r--build/pymake/tests/empty-with-deps.mk4
-rw-r--r--build/pymake/tests/env-var-append.mk7
-rw-r--r--build/pymake/tests/env-var-append2.mk8
-rw-r--r--build/pymake/tests/eof-continuation.mk5
-rw-r--r--build/pymake/tests/escape-chars.mk26
-rw-r--r--build/pymake/tests/escaped-continuation.mk6
-rw-r--r--build/pymake/tests/eval-duringexecute.mk12
-rw-r--r--build/pymake/tests/eval.mk7
-rw-r--r--build/pymake/tests/exit-code.mk5
-rw-r--r--build/pymake/tests/file-functions-symlinks.mk22
-rw-r--r--build/pymake/tests/file-functions.mk19
-rw-r--r--build/pymake/tests/foreach-local-variable.mk8
-rw-r--r--build/pymake/tests/formattingtests.py289
-rw-r--r--build/pymake/tests/func-refs.mk11
-rw-r--r--build/pymake/tests/functions.mk36
-rw-r--r--build/pymake/tests/functiontests.py54
-rw-r--r--build/pymake/tests/if-syntaxerr.mk6
-rw-r--r--build/pymake/tests/ifdefs-nesting.mk13
-rw-r--r--build/pymake/tests/ifdefs.mk127
-rw-r--r--build/pymake/tests/ignore-error.mk13
-rw-r--r--build/pymake/tests/implicit-chain.mk12
-rw-r--r--build/pymake/tests/implicit-dir.mk16
-rw-r--r--build/pymake/tests/implicit-terminal.mk16
-rw-r--r--build/pymake/tests/implicitsubdir.mk12
-rw-r--r--build/pymake/tests/include-dynamic.mk21
-rw-r--r--build/pymake/tests/include-file.inc1
-rw-r--r--build/pymake/tests/include-missing.mk9
-rw-r--r--build/pymake/tests/include-notfound.mk19
-rw-r--r--build/pymake/tests/include-optional-warning.mk4
-rw-r--r--build/pymake/tests/include-regen.mk10
-rw-r--r--build/pymake/tests/include-regen2.mk10
-rw-r--r--build/pymake/tests/include-regen3.mk10
-rw-r--r--build/pymake/tests/include-test.mk8
-rw-r--r--build/pymake/tests/includedeps-norebuild.mk15
-rw-r--r--build/pymake/tests/includedeps-sideeffects.mk10
-rw-r--r--build/pymake/tests/includedeps-stripdotslash.deps1
-rw-r--r--build/pymake/tests/includedeps-stripdotslash.mk8
-rw-r--r--build/pymake/tests/includedeps-variables.deps1
-rw-r--r--build/pymake/tests/includedeps-variables.mk10
-rw-r--r--build/pymake/tests/includedeps.deps1
-rw-r--r--build/pymake/tests/includedeps.mk9
-rw-r--r--build/pymake/tests/info.mk8
-rw-r--r--build/pymake/tests/justprint-native.mk28
-rw-r--r--build/pymake/tests/justprint.mk5
-rw-r--r--build/pymake/tests/keep-going-doublecolon.mk16
-rw-r--r--build/pymake/tests/keep-going-parallel.mk11
-rw-r--r--build/pymake/tests/keep-going.mk14
-rw-r--r--build/pymake/tests/line-continuations.mk24
-rw-r--r--build/pymake/tests/link-search.mk7
-rw-r--r--build/pymake/tests/makeflags.mk7
-rw-r--r--build/pymake/tests/matchany.mk14
-rw-r--r--build/pymake/tests/matchany2.mk13
-rw-r--r--build/pymake/tests/matchany3.mk10
-rw-r--r--build/pymake/tests/mkdir-fail.mk7
-rw-r--r--build/pymake/tests/mkdir.mk27
-rw-r--r--build/pymake/tests/multiple-rules-prerequisite-merge.mk25
-rw-r--r--build/pymake/tests/native-command-delay-load.mk12
-rw-r--r--build/pymake/tests/native-command-raise.mk9
-rw-r--r--build/pymake/tests/native-command-return-fail1.mk8
-rw-r--r--build/pymake/tests/native-command-return-fail2.mk8
-rw-r--r--build/pymake/tests/native-command-return.mk11
-rw-r--r--build/pymake/tests/native-command-shell-glob.mk11
-rw-r--r--build/pymake/tests/native-command-sys-exit-fail1.mk8
-rw-r--r--build/pymake/tests/native-command-sys-exit-fail2.mk8
-rw-r--r--build/pymake/tests/native-command-sys-exit.mk11
-rw-r--r--build/pymake/tests/native-environment.mk11
-rw-r--r--build/pymake/tests/native-pycommandpath-sep.mk21
-rw-r--r--build/pymake/tests/native-pycommandpath.mk15
-rw-r--r--build/pymake/tests/native-simple.mk12
-rw-r--r--build/pymake/tests/native-touch.mk15
-rw-r--r--build/pymake/tests/newlines.mk30
-rw-r--r--build/pymake/tests/no-remake.mk7
-rw-r--r--build/pymake/tests/nosuchfile.mk4
-rw-r--r--build/pymake/tests/notargets.mk5
-rw-r--r--build/pymake/tests/notparallel.mk8
-rw-r--r--build/pymake/tests/oneline-command-continuations.mk5
-rw-r--r--build/pymake/tests/override-propagate.mk37
-rw-r--r--build/pymake/tests/parallel-dep-resolution.mk8
-rw-r--r--build/pymake/tests/parallel-dep-resolution2.mk9
-rw-r--r--build/pymake/tests/parallel-native.mk21
-rw-r--r--build/pymake/tests/parallel-simple.mk27
-rw-r--r--build/pymake/tests/parallel-submake.mk17
-rw-r--r--build/pymake/tests/parallel-toserial.mk31
-rw-r--r--build/pymake/tests/parallel-waiting.mk21
-rw-r--r--build/pymake/tests/parentheses.mk2
-rw-r--r--build/pymake/tests/parsertests.py314
-rw-r--r--build/pymake/tests/path-length.mk9
-rwxr-xr-xbuild/pymake/tests/pathdir/pathtest2
-rw-r--r--build/pymake/tests/pathdir/pathtest.exebin0 -> 45056 bytes
-rw-r--r--build/pymake/tests/pathdir/src/Makefile2
-rw-r--r--build/pymake/tests/pathdir/src/pathtest.cpp6
-rw-r--r--build/pymake/tests/patsubst.mk7
-rw-r--r--build/pymake/tests/phony.mk10
-rw-r--r--build/pymake/tests/pycmd.py38
-rw-r--r--build/pymake/tests/recursive-set.mk7
-rw-r--r--build/pymake/tests/recursive-set2.mk8
-rw-r--r--build/pymake/tests/remake-mtime.mk14
-rw-r--r--build/pymake/tests/rm-fail.mk7
-rw-r--r--build/pymake/tests/rm.mk21
-rw-r--r--build/pymake/tests/runtests.py215
-rw-r--r--build/pymake/tests/serial-dep-resolution.mk5
-rw-r--r--build/pymake/tests/serial-doublecolon-execution.mk18
-rw-r--r--build/pymake/tests/serial-rule-execution.mk5
-rw-r--r--build/pymake/tests/serial-rule-execution2.mk13
-rw-r--r--build/pymake/tests/serial-toparallel.mk5
-rw-r--r--build/pymake/tests/shellfunc.mk7
-rw-r--r--build/pymake/tests/simple-makeflags.mk10
-rw-r--r--build/pymake/tests/sort.mk4
-rw-r--r--build/pymake/tests/specified-target.mk7
-rw-r--r--build/pymake/tests/static-pattern.mk5
-rw-r--r--build/pymake/tests/static-pattern2.mk10
-rw-r--r--build/pymake/tests/subdir/delayload.py1
-rw-r--r--build/pymake/tests/subdir/pymod.py5
-rw-r--r--build/pymake/tests/subdir/testmodule.py3
-rw-r--r--build/pymake/tests/submake-path.makefile211
-rw-r--r--build/pymake/tests/submake-path.mk16
-rw-r--r--build/pymake/tests/submake.makefile224
-rw-r--r--build/pymake/tests/submake.mk16
-rw-r--r--build/pymake/tests/subprocess-path.mk32
-rw-r--r--build/pymake/tests/tab-intro.mk16
-rw-r--r--build/pymake/tests/target-specific.mk30
-rw-r--r--build/pymake/tests/unexport.mk15
-rw-r--r--build/pymake/tests/unexport.submk15
-rw-r--r--build/pymake/tests/unterminated-dollar.mk6
-rw-r--r--build/pymake/tests/var-change-flavor.mk12
-rw-r--r--build/pymake/tests/var-commandline.mk8
-rw-r--r--build/pymake/tests/var-overrides.mk21
-rw-r--r--build/pymake/tests/var-ref.mk19
-rw-r--r--build/pymake/tests/var-set.mk55
-rw-r--r--build/pymake/tests/var-substitutions.mk49
-rw-r--r--build/pymake/tests/vpath-directive-dynamic.mk12
-rw-r--r--build/pymake/tests/vpath-directive.mk31
-rw-r--r--build/pymake/tests/vpath.mk18
-rw-r--r--build/pymake/tests/vpath2.mk18
-rw-r--r--build/pymake/tests/wildcards.mk22
-rw-r--r--build/pymake/tests/windows-paths.mk5
163 files changed, 3318 insertions, 0 deletions
diff --git a/build/pymake/tests/automatic-variables.mk b/build/pymake/tests/automatic-variables.mk
new file mode 100644
index 000000000..5302c08ea
--- /dev/null
+++ b/build/pymake/tests/automatic-variables.mk
@@ -0,0 +1,79 @@
+$(shell \
+mkdir -p src/subd; \
+mkdir subd; \
+touch dummy; \
+sleep 2; \
+touch subd/test.out src/subd/test.in2; \
+sleep 2; \
+touch subd/test.out2 src/subd/test.in; \
+sleep 2; \
+touch subd/host_test.out subd/host_test.out2; \
+sleep 2; \
+touch host_prog; \
+)
+
+VPATH = src
+
+all: prog host_prog prog dir/
+ test "$@" = "all"
+ test "$<" = "prog"
+ test "$^" = "prog host_prog dir"
+ test "$?" = "prog host_prog dir"
+ test "$+" = "prog host_prog prog dir"
+ test "$(@D)" = "."
+ test "$(@F)" = "all"
+ test "$(<D)" = "."
+ test "$(<F)" = "prog"
+ test "$(^D)" = ". . ."
+ test "$(^F)" = "prog host_prog dir"
+ test "$(?D)" = ". . ."
+ test "$(?F)" = "prog host_prog dir"
+ test "$(+D)" = ". . . ."
+ test "$(+F)" = "prog host_prog prog dir"
+ @echo TEST-PASS
+
+dir/:
+ test "$@" = "dir"
+ test "$<" = ""
+ test "$^" = ""
+ test "$(@D)" = "."
+ test "$(@F)" = "dir"
+ mkdir $@
+
+prog: subd/test.out subd/test.out2
+ test "$@" = "prog"
+ test "$<" = "subd/test.out"
+ test "$^" = "subd/test.out subd/test.out2" # ^
+ test "$?" = "subd/test.out subd/test.out2" # ?
+ cat $<
+ test "$$(cat $<)" = "remade"
+ test "$$(cat $(word 2,$^))" = ""
+
+host_prog: subd/host_test.out subd/host_test.out2
+ @echo TEST-FAIL No need to remake
+
+%.out: %.in dummy
+ test "$@" = "subd/test.out"
+ test "$*" = "subd/test" # *
+ test "$<" = "src/subd/test.in" # <
+ test "$^" = "src/subd/test.in dummy" # ^
+ test "$?" = "src/subd/test.in" # ?
+ test "$+" = "src/subd/test.in dummy" # +
+ test "$(@D)" = "subd"
+ test "$(@F)" = "test.out"
+ test "$(*D)" = "subd"
+ test "$(*F)" = "test"
+ test "$(<D)" = "src/subd"
+ test "$(<F)" = "test.in"
+ test "$(^D)" = "src/subd ." # ^D
+ test "$(^F)" = "test.in dummy"
+ test "$(?D)" = "src/subd"
+ test "$(?F)" = "test.in"
+ test "$(+D)" = "src/subd ." # +D
+ test "$(+F)" = "test.in dummy"
+ printf "remade" >$@
+
+%.out2: %.in2 dummy
+ @echo TEST_FAIL No need to remake
+
+.PHONY: all
diff --git a/build/pymake/tests/bad-command-continuation.mk b/build/pymake/tests/bad-command-continuation.mk
new file mode 100644
index 000000000..d9ceccfc2
--- /dev/null
+++ b/build/pymake/tests/bad-command-continuation.mk
@@ -0,0 +1,3 @@
+all:
+ echo 'hello'\
+TEST-PASS
diff --git a/build/pymake/tests/call.mk b/build/pymake/tests/call.mk
new file mode 100644
index 000000000..9eeb7e00c
--- /dev/null
+++ b/build/pymake/tests/call.mk
@@ -0,0 +1,12 @@
+test = $0
+reverse = $2 $1
+twice = $1$1
+sideeffect = $(shell echo "called$1:" >>dummyfile)
+
+all:
+ test "$(call test)" = "test"
+ test "$(call reverse,1,2)" = "2 1"
+# expansion happens *before* substitution, thank sanity
+ test "$(call twice,$(sideeffect))" = ""
+ test `cat dummyfile` = "called:"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/cmd-stripdotslash.mk b/build/pymake/tests/cmd-stripdotslash.mk
new file mode 100644
index 000000000..ce5ed4244
--- /dev/null
+++ b/build/pymake/tests/cmd-stripdotslash.mk
@@ -0,0 +1,5 @@
+all:
+ $(MAKE) -f $(TESTPATH)/cmd-stripdotslash.mk ./foo
+
+./foo:
+ @echo TEST-PASS
diff --git a/build/pymake/tests/cmdgoals.mk b/build/pymake/tests/cmdgoals.mk
new file mode 100644
index 000000000..a3b25e751
--- /dev/null
+++ b/build/pymake/tests/cmdgoals.mk
@@ -0,0 +1,9 @@
+default:
+ test "$(MAKECMDGOALS)" = ""
+ $(MAKE) -f $(TESTPATH)/cmdgoals.mk t1 t2
+ @echo TEST-PASS
+
+t1:
+ test "$(MAKECMDGOALS)" = "t1 t2"
+
+t2:
diff --git a/build/pymake/tests/commandmodifiers.mk b/build/pymake/tests/commandmodifiers.mk
new file mode 100644
index 000000000..8440462f3
--- /dev/null
+++ b/build/pymake/tests/commandmodifiers.mk
@@ -0,0 +1,21 @@
+define COMMAND
+$(1)
+ $(1)
+
+endef
+
+all:
+ $(call COMMAND,@true #TEST-FAIL)
+ $(call COMMAND,-exit 4)
+ $(call COMMAND,@-exit 1 # TEST-FAIL)
+ $(call COMMAND,-@exit 1 # TEST-FAIL)
+ $(call COMMAND,+exit 0)
+ $(call COMMAND,+-exit 1)
+ $(call COMMAND,@+exit 0 # TEST-FAIL)
+ $(call COMMAND,+@exit 0 # TEST-FAIL)
+ $(call COMMAND,-+@exit 1 # TEST-FAIL)
+ $(call COMMAND,+-@exit 1 # TEST-FAIL)
+ $(call COMMAND,@+-exit 1 # TEST-FAIL)
+ $(call COMMAND,@+-@+-exit 1 # TEST-FAIL)
+ $(call COMMAND,@@++exit 0 # TEST-FAIL)
+ @echo TEST-PASS
diff --git a/build/pymake/tests/comment-parsing.mk b/build/pymake/tests/comment-parsing.mk
new file mode 100644
index 000000000..d469e1aea
--- /dev/null
+++ b/build/pymake/tests/comment-parsing.mk
@@ -0,0 +1,29 @@
+# where do comments take effect?
+
+VAR = val1 # comment
+VAR2 = lit2\#hash
+VAR2_1 = lit2.1\\\#hash
+VAR3 = val3
+VAR4 = lit4\\#backslash
+VAR4_1 = lit4\\\\#backslash
+VAR5 = lit5\char
+VAR6 = lit6\\char
+VAR7 = lit7\\
+VAR8 = lit8\\\\
+VAR9 = lit9\\\\extra
+# This comment extends to the next line \
+VAR3 = ignored
+
+all:
+ test "$(VAR)" = "val1 "
+ test "$(VAR2)" = "lit2#hash"
+ test '$(VAR2_1)' = 'lit2.1\#hash'
+ test "$(VAR3)" = "val3"
+ test '$(VAR4)' = 'lit4\'
+ test '$(VAR4_1)' = 'lit4\\'
+ test '$(VAR5)' = 'lit5\char'
+ test '$(VAR6)' = 'lit6\\char'
+ test '$(VAR7)' = 'lit7\\'
+ test '$(VAR8)' = 'lit8\\\\'
+ test '$(VAR9)' = 'lit9\\\\extra'
+ @echo "TEST-PASS"
diff --git a/build/pymake/tests/continuations-in-functions.mk b/build/pymake/tests/continuations-in-functions.mk
new file mode 100644
index 000000000..533df6176
--- /dev/null
+++ b/build/pymake/tests/continuations-in-functions.mk
@@ -0,0 +1,6 @@
+all:
+ test 'Hello world.' = '$(if 1,Hello \
+ world.)'
+ test '(Hello world.)' != '(Hello \
+ world.)'
+ @echo TEST-PASS
diff --git a/build/pymake/tests/datatests.py b/build/pymake/tests/datatests.py
new file mode 100644
index 000000000..513028b0b
--- /dev/null
+++ b/build/pymake/tests/datatests.py
@@ -0,0 +1,237 @@
+import pymake.data, pymake.functions, pymake.util
+import unittest
+import re
+from cStringIO import StringIO
+
+def multitest(cls):
+ for name in cls.testdata.iterkeys():
+ def m(self, name=name):
+ return self.runSingle(*self.testdata[name])
+
+ setattr(cls, 'test_%s' % name, m)
+ return cls
+
+class SplitWordsTest(unittest.TestCase):
+ testdata = (
+ (' test test.c test.o ', ['test', 'test.c', 'test.o']),
+ ('\ttest\t test.c \ntest.o', ['test', 'test.c', 'test.o']),
+ )
+
+ def runTest(self):
+ for s, e in self.testdata:
+ w = s.split()
+ self.assertEqual(w, e, 'splitwords(%r)' % (s,))
+
+class GetPatSubstTest(unittest.TestCase):
+ testdata = (
+ ('%.c', '%.o', ' test test.c test.o ', 'test test.o test.o'),
+ ('%', '%.o', ' test.c test.o ', 'test.c.o test.o.o'),
+ ('foo', 'bar', 'test foo bar', 'test bar bar'),
+ ('foo', '%bar', 'test foo bar', 'test %bar bar'),
+ ('%', 'perc_%', 'path', 'perc_path'),
+ ('\\%', 'sub%', 'p %', 'p sub%'),
+ ('%.c', '\\%%.o', 'foo.c bar.o baz.cpp', '%foo.o bar.o baz.cpp'),
+ )
+
+ def runTest(self):
+ for s, r, d, e in self.testdata:
+ words = d.split()
+ p = pymake.data.Pattern(s)
+ a = ' '.join((p.subst(r, word, False)
+ for word in words))
+ self.assertEqual(a, e, 'Pattern(%r).subst(%r, %r)' % (s, r, d))
+
+class LRUTest(unittest.TestCase):
+ # getkey, expected, funccount, debugitems
+ expected = (
+ (0, '', 1, (0,)),
+ (0, '', 2, (0,)),
+ (1, ' ', 3, (1, 0)),
+ (1, ' ', 3, (1, 0)),
+ (0, '', 4, (0, 1)),
+ (2, ' ', 5, (2, 0, 1)),
+ (1, ' ', 5, (1, 2, 0)),
+ (3, ' ', 6, (3, 1, 2)),
+ )
+
+ def spaceFunc(self, l):
+ self.funccount += 1
+ return ''.ljust(l)
+
+ def runTest(self):
+ self.funccount = 0
+ c = pymake.util.LRUCache(3, self.spaceFunc, lambda k, v: k % 2)
+ self.assertEqual(tuple(c.debugitems()), ())
+
+ for i in xrange(0, len(self.expected)):
+ k, e, fc, di = self.expected[i]
+
+ v = c.get(k)
+ self.assertEqual(v, e)
+ self.assertEqual(self.funccount, fc,
+ "funccount, iteration %i, got %i expected %i" % (i, self.funccount, fc))
+ goti = tuple(c.debugitems())
+ self.assertEqual(goti, di,
+ "debugitems, iteration %i, got %r expected %r" % (i, goti, di))
+
+class EqualityTest(unittest.TestCase):
+ def test_string_expansion(self):
+ s1 = pymake.data.StringExpansion('foo bar', None)
+ s2 = pymake.data.StringExpansion('foo bar', None)
+
+ self.assertEqual(s1, s2)
+
+ def test_expansion_simple(self):
+ s1 = pymake.data.Expansion(None)
+ s2 = pymake.data.Expansion(None)
+
+ self.assertEqual(s1, s2)
+
+ s1.appendstr('foo')
+ s2.appendstr('foo')
+ self.assertEqual(s1, s2)
+
+ def test_expansion_string_finish(self):
+ """Adjacent strings should normalize to same value."""
+ s1 = pymake.data.Expansion(None)
+ s2 = pymake.data.Expansion(None)
+
+ s1.appendstr('foo')
+ s2.appendstr('foo')
+
+ s1.appendstr(' bar')
+ s1.appendstr(' baz')
+ s2.appendstr(' bar baz')
+
+ self.assertEqual(s1, s2)
+
+ def test_function(self):
+ s1 = pymake.data.Expansion(None)
+ s2 = pymake.data.Expansion(None)
+
+ n1 = pymake.data.StringExpansion('FOO', None)
+ n2 = pymake.data.StringExpansion('FOO', None)
+
+ v1 = pymake.functions.VariableRef(None, n1)
+ v2 = pymake.functions.VariableRef(None, n2)
+
+ s1.appendfunc(v1)
+ s2.appendfunc(v2)
+
+ self.assertEqual(s1, s2)
+
+
+class StringExpansionTest(unittest.TestCase):
+ def test_base_expansion_interface(self):
+ s1 = pymake.data.StringExpansion('FOO', None)
+
+ self.assertTrue(s1.is_static_string)
+
+ funcs = list(s1.functions())
+ self.assertEqual(len(funcs), 0)
+
+ funcs = list(s1.functions(True))
+ self.assertEqual(len(funcs), 0)
+
+ refs = list(s1.variable_references())
+ self.assertEqual(len(refs), 0)
+
+
+class ExpansionTest(unittest.TestCase):
+ def test_is_static_string(self):
+ e1 = pymake.data.Expansion()
+ e1.appendstr('foo')
+
+ self.assertTrue(e1.is_static_string)
+
+ e1.appendstr('bar')
+ self.assertTrue(e1.is_static_string)
+
+ vname = pymake.data.StringExpansion('FOO', None)
+ func = pymake.functions.VariableRef(None, vname)
+
+ e1.appendfunc(func)
+
+ self.assertFalse(e1.is_static_string)
+
+ def test_get_functions(self):
+ e1 = pymake.data.Expansion()
+ e1.appendstr('foo')
+
+ vname1 = pymake.data.StringExpansion('FOO', None)
+ vname2 = pymake.data.StringExpansion('BAR', None)
+
+ func1 = pymake.functions.VariableRef(None, vname1)
+ func2 = pymake.functions.VariableRef(None, vname2)
+
+ e1.appendfunc(func1)
+ e1.appendfunc(func2)
+
+ funcs = list(e1.functions())
+ self.assertEqual(len(funcs), 2)
+
+ func3 = pymake.functions.SortFunction(None)
+ func3.append(vname1)
+
+ e1.appendfunc(func3)
+
+ funcs = list(e1.functions())
+ self.assertEqual(len(funcs), 3)
+
+ refs = list(e1.variable_references())
+ self.assertEqual(len(refs), 2)
+
+ def test_get_functions_descend(self):
+ e1 = pymake.data.Expansion()
+ vname1 = pymake.data.StringExpansion('FOO', None)
+ func1 = pymake.functions.VariableRef(None, vname1)
+ e2 = pymake.data.Expansion()
+ e2.appendfunc(func1)
+
+ func2 = pymake.functions.SortFunction(None)
+ func2.append(e2)
+
+ e1.appendfunc(func2)
+
+ funcs = list(e1.functions())
+ self.assertEqual(len(funcs), 1)
+
+ funcs = list(e1.functions(True))
+ self.assertEqual(len(funcs), 2)
+
+ self.assertTrue(isinstance(funcs[0], pymake.functions.SortFunction))
+
+ def test_is_filesystem_dependent(self):
+ e = pymake.data.Expansion()
+ vname1 = pymake.data.StringExpansion('FOO', None)
+ func1 = pymake.functions.VariableRef(None, vname1)
+ e.appendfunc(func1)
+
+ self.assertFalse(e.is_filesystem_dependent)
+
+ func2 = pymake.functions.WildcardFunction(None)
+ func2.append(vname1)
+ e.appendfunc(func2)
+
+ self.assertTrue(e.is_filesystem_dependent)
+
+ def test_is_filesystem_dependent_descend(self):
+ sort = pymake.functions.SortFunction(None)
+ wildcard = pymake.functions.WildcardFunction(None)
+
+ e = pymake.data.StringExpansion('foo/*', None)
+ wildcard.append(e)
+
+ e = pymake.data.Expansion(None)
+ e.appendfunc(wildcard)
+
+ sort.append(e)
+
+ e = pymake.data.Expansion(None)
+ e.appendfunc(sort)
+
+ self.assertTrue(e.is_filesystem_dependent)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/pymake/tests/default-goal-set-first.mk b/build/pymake/tests/default-goal-set-first.mk
new file mode 100644
index 000000000..00a5b53a2
--- /dev/null
+++ b/build/pymake/tests/default-goal-set-first.mk
@@ -0,0 +1,7 @@
+.DEFAULT_GOAL := default
+
+not-default:
+ @echo TEST-FAIL did not run default rule
+
+default:
+ @echo TEST-PASS
diff --git a/build/pymake/tests/default-goal.mk b/build/pymake/tests/default-goal.mk
new file mode 100644
index 000000000..699d6c0cd
--- /dev/null
+++ b/build/pymake/tests/default-goal.mk
@@ -0,0 +1,8 @@
+not-default:
+ @echo TEST-FAIL did not run default rule
+
+default:
+ @echo $(if $(filter not-default,$(INTERMEDIATE_DEFAULT_GOAL)),TEST-PASS,TEST-FAIL .DEFAULT_GOAL not set by $(MAKE))
+
+INTERMEDIATE_DEFAULT_GOAL := $(.DEFAULT_GOAL)
+.DEFAULT_GOAL := default
diff --git a/build/pymake/tests/default-target.mk b/build/pymake/tests/default-target.mk
new file mode 100644
index 000000000..701ac6916
--- /dev/null
+++ b/build/pymake/tests/default-target.mk
@@ -0,0 +1,14 @@
+test: VAR = value
+
+%.do:
+ @echo TEST-FAIL: ran target "$@", should have run "all"
+
+.PHONY: test
+
+all:
+ @echo TEST-PASS: the default target is all
+
+test:
+ @echo TEST-FAIL: ran target "$@", should have run "all"
+
+test.do:
diff --git a/build/pymake/tests/default-target2.mk b/build/pymake/tests/default-target2.mk
new file mode 100644
index 000000000..b5a4b1bbf
--- /dev/null
+++ b/build/pymake/tests/default-target2.mk
@@ -0,0 +1,6 @@
+test.foo: %.foo:
+ test "$@" = "test.foo"
+ @echo TEST-PASS made test.foo by default
+
+all:
+ @echo TEST-FAIL made $@, should have made test.foo
diff --git a/build/pymake/tests/define-directive.mk b/build/pymake/tests/define-directive.mk
new file mode 100644
index 000000000..789988666
--- /dev/null
+++ b/build/pymake/tests/define-directive.mk
@@ -0,0 +1,69 @@
+define COMMANDS
+shellvar=hello
+test "$$shellvar" != "hello"
+endef
+
+define COMMANDS2
+shellvar=hello; \
+ test "$$shellvar" = "hello"
+endef
+
+define VARWITHCOMMENT # comment
+value
+endef
+
+define TEST3
+ whitespace
+endef
+
+define TEST4
+define TEST5
+random
+endef
+ endef
+
+ifdef TEST5
+$(error TEST5 should not be set)
+endif
+
+define TEST6
+ define TEST7
+random
+endef
+endef
+
+ifdef TEST7
+$(error TEST7 should not be set)
+endif
+
+define TEST8
+is this # a comment?
+endef
+
+ifneq ($(TEST8),is this \# a comment?)
+$(error TEST8 value not expected: $(TEST8))
+endif
+
+# A backslash continuation "hides" the endef
+define TEST9
+value \
+endef
+endef
+
+# Test ridiculous spacing
+ define TEST10
+ define TEST11
+ baz
+endef
+define TEST12
+ foo
+ endef
+ endef
+
+all:
+ $(COMMANDS)
+ $(COMMANDS2)
+ test '$(VARWITHCOMMENT)' = 'value'
+ test '$(COMMANDS2)' = 'shellvar=hello; test "$$shellvar" = "hello"'
+ test "$(TEST3)" = " whitespace"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/depfailed.mk b/build/pymake/tests/depfailed.mk
new file mode 100644
index 000000000..ce4137c38
--- /dev/null
+++ b/build/pymake/tests/depfailed.mk
@@ -0,0 +1,4 @@
+#T returncode: 2
+
+all: foo.out foo.in
+ @echo TEST-PASS
diff --git a/build/pymake/tests/depfailedj.mk b/build/pymake/tests/depfailedj.mk
new file mode 100644
index 000000000..a94c74f6f
--- /dev/null
+++ b/build/pymake/tests/depfailedj.mk
@@ -0,0 +1,10 @@
+#T returncode: 2
+#T commandline: ['-j4']
+
+$(shell touch foo.in)
+
+all: foo.in foo.out missing
+ @echo TEST-PASS
+
+%.out: %.in
+ cp $< $@
diff --git a/build/pymake/tests/diamond-deps.mk b/build/pymake/tests/diamond-deps.mk
new file mode 100644
index 000000000..40a4176d9
--- /dev/null
+++ b/build/pymake/tests/diamond-deps.mk
@@ -0,0 +1,13 @@
+# If the dependency graph includes a diamond dependency, we should only remake
+# once!
+
+all: depA depB
+ cat testfile
+ test `cat testfile` = "data";
+ @echo TEST-PASS
+
+depA: testfile
+depB: testfile
+
+testfile:
+ printf "data" >>$@
diff --git a/build/pymake/tests/dotslash-dir.mk b/build/pymake/tests/dotslash-dir.mk
new file mode 100644
index 000000000..8b30d1e3c
--- /dev/null
+++ b/build/pymake/tests/dotslash-dir.mk
@@ -0,0 +1,8 @@
+#T grep-for: "dotslash-built"
+.PHONY: $(dir foo)
+
+all: $(dir foo)
+ @echo TEST-PASS
+
+$(dir foo):
+ @echo dotslash-built
diff --git a/build/pymake/tests/dotslash-parse.mk b/build/pymake/tests/dotslash-parse.mk
new file mode 100644
index 000000000..91461bedb
--- /dev/null
+++ b/build/pymake/tests/dotslash-parse.mk
@@ -0,0 +1,4 @@
+./:
+
+# This is merely a test to see that pymake doesn't choke on parsing ./
+$(info TEST-PASS)
diff --git a/build/pymake/tests/dotslash-phony.mk b/build/pymake/tests/dotslash-phony.mk
new file mode 100644
index 000000000..06b6ae78d
--- /dev/null
+++ b/build/pymake/tests/dotslash-phony.mk
@@ -0,0 +1,3 @@
+.PHONY: ./
+./:
+ @echo TEST-PASS
diff --git a/build/pymake/tests/dotslash.mk b/build/pymake/tests/dotslash.mk
new file mode 100644
index 000000000..585db96b7
--- /dev/null
+++ b/build/pymake/tests/dotslash.mk
@@ -0,0 +1,9 @@
+$(shell touch foo.in)
+
+all: foo.out
+ test "$(wildcard ./*.in)" = "./foo.in"
+ @echo TEST-PASS
+
+./%.out: %.in
+ test "$@" = "foo.out"
+ cp $< $@
diff --git a/build/pymake/tests/doublecolon-exists.mk b/build/pymake/tests/doublecolon-exists.mk
new file mode 100644
index 000000000..5d99a1f6b
--- /dev/null
+++ b/build/pymake/tests/doublecolon-exists.mk
@@ -0,0 +1,16 @@
+$(shell touch foo.testfile1 foo.testfile2)
+
+# when a rule has commands and no prerequisites, should it be executed?
+# double-colon: yes
+# single-colon: no
+
+all: foo.testfile1 foo.testfile2
+ test "$$(cat foo.testfile1)" = ""
+ test "$$(cat foo.testfile2)" = "remade:foo.testfile2"
+ @echo TEST-PASS
+
+foo.testfile1:
+ @echo TEST-FAIL
+
+foo.testfile2::
+ printf "remade:$@"> $@
diff --git a/build/pymake/tests/doublecolon-priordeps.mk b/build/pymake/tests/doublecolon-priordeps.mk
new file mode 100644
index 000000000..6cdf3a8e7
--- /dev/null
+++ b/build/pymake/tests/doublecolon-priordeps.mk
@@ -0,0 +1,19 @@
+#T commandline: ['-j3']
+
+# All *prior* dependencies of a doublecolon rule must be satisfied before
+# subsequent commands are run.
+
+all:: target1
+
+all:: target2
+ test -f target1
+ @echo TEST-PASS
+
+target1:
+ touch starting-$@
+ sleep 1
+ touch $@
+
+target2:
+ sleep 0.1
+ test -f starting-target1
diff --git a/build/pymake/tests/doublecolon-remake.mk b/build/pymake/tests/doublecolon-remake.mk
new file mode 100644
index 000000000..52aa9265c
--- /dev/null
+++ b/build/pymake/tests/doublecolon-remake.mk
@@ -0,0 +1,4 @@
+$(shell touch somefile)
+
+all:: somefile
+ @echo TEST-PASS
diff --git a/build/pymake/tests/dynamic-var.mk b/build/pymake/tests/dynamic-var.mk
new file mode 100644
index 000000000..0993b9ccf
--- /dev/null
+++ b/build/pymake/tests/dynamic-var.mk
@@ -0,0 +1,18 @@
+# The *name* of variables can be constructed dynamically.
+
+VARNAME = FOOBAR
+
+$(VARNAME) = foovalue
+$(VARNAME)2 = foo2value
+
+$(VARNAME:%BAR=%BAM) = foobam
+
+all:
+ test "$(FOOBAR)" = "foovalue"
+ test "$(flavor FOOBAZ)" = "undefined"
+ test "$(FOOBAR2)" = "bazvalue"
+ test "$(FOOBAM)" = "foobam"
+ @echo TEST-PASS
+
+VARNAME = FOOBAZ
+FOOBAR2 = bazvalue
diff --git a/build/pymake/tests/empty-arg.mk b/build/pymake/tests/empty-arg.mk
new file mode 100644
index 000000000..616e5b694
--- /dev/null
+++ b/build/pymake/tests/empty-arg.mk
@@ -0,0 +1,2 @@
+all:
+ @ sh -c 'if [ $$# = 3 ] ; then echo TEST-PASS; else echo TEST-FAIL; fi' -- a "" b
diff --git a/build/pymake/tests/empty-command-semicolon.mk b/build/pymake/tests/empty-command-semicolon.mk
new file mode 100644
index 000000000..07789f3f1
--- /dev/null
+++ b/build/pymake/tests/empty-command-semicolon.mk
@@ -0,0 +1,5 @@
+all:
+ @echo TEST-PASS
+
+foo: ;
+
diff --git a/build/pymake/tests/empty-with-deps.mk b/build/pymake/tests/empty-with-deps.mk
new file mode 100644
index 000000000..284e5a113
--- /dev/null
+++ b/build/pymake/tests/empty-with-deps.mk
@@ -0,0 +1,4 @@
+default.test: default.c
+
+default.c:
+ @echo TEST-PASS
diff --git a/build/pymake/tests/env-var-append.mk b/build/pymake/tests/env-var-append.mk
new file mode 100644
index 000000000..4db39c45f
--- /dev/null
+++ b/build/pymake/tests/env-var-append.mk
@@ -0,0 +1,7 @@
+#T environment: {'FOO': 'TEST'}
+
+FOO += $(BAR)
+BAR := PASS
+
+all:
+ @echo $(subst $(NULL) ,-,$(FOO))
diff --git a/build/pymake/tests/env-var-append2.mk b/build/pymake/tests/env-var-append2.mk
new file mode 100644
index 000000000..fc0735d88
--- /dev/null
+++ b/build/pymake/tests/env-var-append2.mk
@@ -0,0 +1,8 @@
+#T environment: {'FOO': '$(BAZ)'}
+
+FOO += $(BAR)
+BAR := PASS
+BAZ := TEST
+
+all:
+ @echo $(subst $(NULL) ,-,$(FOO))
diff --git a/build/pymake/tests/eof-continuation.mk b/build/pymake/tests/eof-continuation.mk
new file mode 100644
index 000000000..daeaabc3e
--- /dev/null
+++ b/build/pymake/tests/eof-continuation.mk
@@ -0,0 +1,5 @@
+all:
+ test '$(TESTVAR)' = 'testval\'
+ @echo TEST-PASS
+
+TESTVAR = testval\ \ No newline at end of file
diff --git a/build/pymake/tests/escape-chars.mk b/build/pymake/tests/escape-chars.mk
new file mode 100644
index 000000000..ebea33074
--- /dev/null
+++ b/build/pymake/tests/escape-chars.mk
@@ -0,0 +1,26 @@
+space = $(NULL) $(NULL)
+hello$(space)world$(space) = hellovalue
+
+A = aval
+
+VAR = value1\\
+VARAWFUL = value1\\#comment
+VAR2 = value2
+VAR3 = test\$A
+VAR4 = value4\\value5
+
+VAR5 = value1\\ \ \
+ value2
+
+EPERCENT = \%
+
+all:
+ test "$(hello world )" = "hellovalue"
+ test "$(VAR)" = "value1\\"
+ test '$(VARAWFUL)' = 'value1\'
+ test "$(VAR2)" = "value2"
+ test "$(VAR3)" = "test\aval"
+ test "$(VAR4)" = "value4\\value5"
+ test "$(VAR5)" = "value1\\ \ value2"
+ test "$(EPERCENT)" = "\%"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/escaped-continuation.mk b/build/pymake/tests/escaped-continuation.mk
new file mode 100644
index 000000000..537f7547f
--- /dev/null
+++ b/build/pymake/tests/escaped-continuation.mk
@@ -0,0 +1,6 @@
+#T returncode: 2
+
+all:
+ echo "Hello" \\
+ test "world" = "not!"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/eval-duringexecute.mk b/build/pymake/tests/eval-duringexecute.mk
new file mode 100644
index 000000000..dff848032
--- /dev/null
+++ b/build/pymake/tests/eval-duringexecute.mk
@@ -0,0 +1,12 @@
+#T returncode: 2
+
+# Once parsing is finished, recursive expansion in commands are not allowed to create any new rules (it may only set variables)
+
+define MORERULE
+all:
+ @echo TEST-FAIL
+endef
+
+all:
+ $(eval $(MORERULE))
+ @echo done
diff --git a/build/pymake/tests/eval.mk b/build/pymake/tests/eval.mk
new file mode 100644
index 000000000..de9759f02
--- /dev/null
+++ b/build/pymake/tests/eval.mk
@@ -0,0 +1,7 @@
+TESTVAR = val1
+
+$(eval TESTVAR = val2)
+
+all:
+ test "$(TESTVAR)" = "val2"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/exit-code.mk b/build/pymake/tests/exit-code.mk
new file mode 100644
index 000000000..84dcffcf9
--- /dev/null
+++ b/build/pymake/tests/exit-code.mk
@@ -0,0 +1,5 @@
+#T returncode: 2
+
+all:
+ exit 1
+ @echo TEST-PASS
diff --git a/build/pymake/tests/file-functions-symlinks.mk b/build/pymake/tests/file-functions-symlinks.mk
new file mode 100644
index 000000000..dcc0f6eef
--- /dev/null
+++ b/build/pymake/tests/file-functions-symlinks.mk
@@ -0,0 +1,22 @@
+#T returncode-on: {'win32': 2}
+$(shell \
+touch test.file; \
+ln -s test.file test.symlink; \
+ln -s test.missing missing.symlink; \
+touch .testhidden; \
+mkdir foo; \
+touch foo/testfile; \
+ln -s foo symdir; \
+)
+
+all:
+ test "$(abspath test.file test.symlink)" = "$(CURDIR)/test.file $(CURDIR)/test.symlink"
+ test "$(realpath test.file test.symlink)" = "$(CURDIR)/test.file $(CURDIR)/test.file"
+ test "$(sort $(wildcard *))" = "foo symdir test.file test.symlink"
+ test "$(sort $(wildcard .*))" = ". .. .testhidden"
+ test "$(sort $(wildcard test*))" = "test.file test.symlink"
+ test "$(sort $(wildcard foo/*))" = "foo/testfile"
+ test "$(sort $(wildcard ./*))" = "./foo ./symdir ./test.file ./test.symlink"
+ test "$(sort $(wildcard f?o/*))" = "foo/testfile"
+ test "$(sort $(wildcard */*))" = "foo/testfile symdir/testfile"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/file-functions.mk b/build/pymake/tests/file-functions.mk
new file mode 100644
index 000000000..7e4c68e85
--- /dev/null
+++ b/build/pymake/tests/file-functions.mk
@@ -0,0 +1,19 @@
+$(shell \
+touch test.file; \
+touch .testhidden; \
+mkdir foo; \
+touch foo/testfile; \
+)
+
+all:
+ test "$(abspath test.file)" = "$(CURDIR)/test.file"
+ test "$(realpath test.file)" = "$(CURDIR)/test.file"
+ test "$(sort $(wildcard *))" = "foo test.file"
+# commented out because GNU make matches . and .. while python doesn't, and I don't
+# care enough
+# test "$(sort $(wildcard .*))" = ". .. .testhidden"
+ test "$(sort $(wildcard test*))" = "test.file"
+ test "$(sort $(wildcard foo/*))" = "foo/testfile"
+ test "$(sort $(wildcard ./*))" = "./foo ./test.file"
+ test "$(sort $(wildcard f?o/*))" = "foo/testfile"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/foreach-local-variable.mk b/build/pymake/tests/foreach-local-variable.mk
new file mode 100644
index 000000000..2551621eb
--- /dev/null
+++ b/build/pymake/tests/foreach-local-variable.mk
@@ -0,0 +1,8 @@
+# This test ensures that a local variable in a $(foreach) is bound to
+# the local value, not a global value.
+i := dummy
+
+all:
+ test "$(foreach i,foo bar,found:$(i))" = "found:foo found:bar"
+ test "$(i)" = "dummy"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/formattingtests.py b/build/pymake/tests/formattingtests.py
new file mode 100644
index 000000000..7aad6d4cc
--- /dev/null
+++ b/build/pymake/tests/formattingtests.py
@@ -0,0 +1,289 @@
+# This file contains test code for the formatting of parsed statements back to
+# make file "source." It essentially verifies to to_source() functions
+# scattered across the tree.
+
+import glob
+import logging
+import os.path
+import unittest
+
+from pymake.data import Expansion
+from pymake.data import StringExpansion
+from pymake.functions import BasenameFunction
+from pymake.functions import SubstitutionRef
+from pymake.functions import VariableRef
+from pymake.functions import WordlistFunction
+from pymake.parserdata import Include
+from pymake.parserdata import SetVariable
+from pymake.parser import parsestring
+from pymake.parser import SyntaxError
+
+class TestBase(unittest.TestCase):
+ pass
+
+class VariableRefTest(TestBase):
+ def test_string_name(self):
+ e = StringExpansion('foo', None)
+ v = VariableRef(None, e)
+
+ self.assertEqual(v.to_source(), '$(foo)')
+
+ def test_special_variable(self):
+ e = StringExpansion('<', None)
+ v = VariableRef(None, e)
+
+ self.assertEqual(v.to_source(), '$<')
+
+ def test_expansion_simple(self):
+ e = Expansion()
+ e.appendstr('foo')
+ e.appendstr('bar')
+
+ v = VariableRef(None, e)
+
+ self.assertEqual(v.to_source(), '$(foobar)')
+
+class StandardFunctionTest(TestBase):
+ def test_basename(self):
+ e1 = StringExpansion('foo', None)
+ v = VariableRef(None, e1)
+ e2 = Expansion(None)
+ e2.appendfunc(v)
+
+ b = BasenameFunction(None)
+ b.append(e2)
+
+ self.assertEqual(b.to_source(), '$(basename $(foo))')
+
+ def test_wordlist(self):
+ e1 = StringExpansion('foo', None)
+ e2 = StringExpansion('bar ', None)
+ e3 = StringExpansion(' baz', None)
+
+ w = WordlistFunction(None)
+ w.append(e1)
+ w.append(e2)
+ w.append(e3)
+
+ self.assertEqual(w.to_source(), '$(wordlist foo,bar , baz)')
+
+ def test_curly_brackets(self):
+ e1 = Expansion(None)
+ e1.appendstr('foo')
+
+ e2 = Expansion(None)
+ e2.appendstr('foo ( bar')
+
+ f = WordlistFunction(None)
+ f.append(e1)
+ f.append(e2)
+
+ self.assertEqual(f.to_source(), '${wordlist foo,foo ( bar}')
+
+class StringExpansionTest(TestBase):
+ def test_simple(self):
+ e = StringExpansion('foobar', None)
+ self.assertEqual(e.to_source(), 'foobar')
+
+ e = StringExpansion('$var', None)
+ self.assertEqual(e.to_source(), '$var')
+
+ def test_escaping(self):
+ e = StringExpansion('$var', None)
+ self.assertEqual(e.to_source(escape_variables=True), '$$var')
+
+ e = StringExpansion('this is # not a comment', None)
+ self.assertEqual(e.to_source(escape_comments=True),
+ 'this is \# not a comment')
+
+ def test_empty(self):
+ e = StringExpansion('', None)
+ self.assertEqual(e.to_source(), '')
+
+ e = StringExpansion(' ', None)
+ self.assertEqual(e.to_source(), ' ')
+
+class ExpansionTest(TestBase):
+ def test_single_string(self):
+ e = Expansion()
+ e.appendstr('foo')
+
+ self.assertEqual(e.to_source(), 'foo')
+
+ def test_multiple_strings(self):
+ e = Expansion()
+ e.appendstr('hello')
+ e.appendstr('world')
+
+ self.assertEqual(e.to_source(), 'helloworld')
+
+ def test_string_escape(self):
+ e = Expansion()
+ e.appendstr('$var')
+ self.assertEqual(e.to_source(), '$var')
+ self.assertEqual(e.to_source(escape_variables=True), '$$var')
+
+ e = Expansion()
+ e.appendstr('foo')
+ e.appendstr(' $bar')
+ self.assertEqual(e.to_source(escape_variables=True), 'foo $$bar')
+
+class SubstitutionRefTest(TestBase):
+ def test_simple(self):
+ name = StringExpansion('foo', None)
+ c = StringExpansion('%.c', None)
+ o = StringExpansion('%.o', None)
+ s = SubstitutionRef(None, name, c, o)
+
+ self.assertEqual(s.to_source(), '$(foo:%.c=%.o)')
+
+class SetVariableTest(TestBase):
+ def test_simple(self):
+ v = SetVariable(StringExpansion('foo', None), '=', 'bar', None, None)
+ self.assertEqual(v.to_source(), 'foo = bar')
+
+ def test_multiline(self):
+ s = 'hello\nworld'
+ foo = StringExpansion('FOO', None)
+
+ v = SetVariable(foo, '=', s, None, None)
+
+ self.assertEqual(v.to_source(), 'define FOO\nhello\nworld\nendef')
+
+ def test_multiline_immediate(self):
+ source = 'define FOO :=\nhello\nworld\nendef'
+
+ statements = parsestring(source, 'foo.mk')
+ self.assertEqual(statements.to_source(), source)
+
+ def test_target_specific(self):
+ foo = StringExpansion('FOO', None)
+ bar = StringExpansion('BAR', None)
+
+ v = SetVariable(foo, '+=', 'value', None, bar)
+
+ self.assertEqual(v.to_source(), 'BAR: FOO += value')
+
+class IncludeTest(TestBase):
+ def test_include(self):
+ e = StringExpansion('rules.mk', None)
+ i = Include(e, True, False)
+ self.assertEqual(i.to_source(), 'include rules.mk')
+
+ i = Include(e, False, False)
+ self.assertEqual(i.to_source(), '-include rules.mk')
+
+class IfdefTest(TestBase):
+ def test_simple(self):
+ source = 'ifdef FOO\nbar := $(value)\nendif'
+
+ statements = parsestring(source, 'foo.mk')
+ self.assertEqual(statements[0].to_source(), source)
+
+ def test_nested(self):
+ source = 'ifdef FOO\nifdef BAR\nhello = world\nendif\nendif'
+
+ statements = parsestring(source, 'foo.mk')
+ self.assertEqual(statements[0].to_source(), source)
+
+ def test_negation(self):
+ source = 'ifndef FOO\nbar += value\nendif'
+
+ statements = parsestring(source, 'foo.mk')
+ self.assertEqual(statements[0].to_source(), source)
+
+class IfeqTest(TestBase):
+ def test_simple(self):
+ source = 'ifeq ($(foo),bar)\nhello = $(world)\nendif'
+
+ statements = parsestring(source, 'foo.mk')
+ self.assertEqual(statements[0].to_source(), source)
+
+ def test_negation(self):
+ source = 'ifneq (foo,bar)\nhello = world\nendif'
+
+ statements = parsestring(source, 'foo.mk')
+ self.assertEqual(statements.to_source(), source)
+
+class ConditionBlocksTest(TestBase):
+ def test_mixed_conditions(self):
+ source = 'ifdef FOO\nifeq ($(FOO),bar)\nvar += $(value)\nendif\nendif'
+
+ statements = parsestring(source, 'foo.mk')
+ self.assertEqual(statements.to_source(), source)
+
+ def test_extra_statements(self):
+ source = 'ifdef FOO\nF := 1\nifdef BAR\nB += 1\nendif\nC = 1\nendif'
+
+ statements = parsestring(source, 'foo.mk')
+ self.assertEqual(statements.to_source(), source)
+
+ def test_whitespace_preservation(self):
+ source = "ifeq ' x' 'x '\n$(error stripping)\nendif"
+
+ statements = parsestring(source, 'foo.mk')
+ self.assertEqual(statements.to_source(), source)
+
+ source = 'ifneq (x , x)\n$(error stripping)\nendif'
+ statements = parsestring(source, 'foo.mk')
+ self.assertEqual(statements.to_source(),
+ 'ifneq (x,x)\n$(error stripping)\nendif')
+
+class MakefileCorupusTest(TestBase):
+ """Runs the make files from the pymake corpus through the formatter.
+
+ All the above tests are child's play compared to this.
+ """
+
+ # Our reformatting isn't perfect. We ignore files with known failures until
+ # we make them work.
+ # TODO Address these formatting corner cases.
+ _IGNORE_FILES = [
+ # We are thrown off by backslashes at end of lines.
+ 'comment-parsing.mk',
+ 'escape-chars.mk',
+ 'include-notfound.mk',
+ ]
+
+ def _get_test_files(self):
+ ourdir = os.path.dirname(os.path.abspath(__file__))
+
+ for makefile in glob.glob(os.path.join(ourdir, '*.mk')):
+ if os.path.basename(makefile) in self._IGNORE_FILES:
+ continue
+
+ source = None
+ with open(makefile, 'rU') as fh:
+ source = fh.read()
+
+ try:
+ yield (makefile, source, parsestring(source, makefile))
+ except SyntaxError:
+ continue
+
+ def test_reparse_consistency(self):
+ for filename, source, statements in self._get_test_files():
+ reformatted = statements.to_source()
+
+ # We should be able to parse the reformatted source fine.
+ new_statements = parsestring(reformatted, filename)
+
+ # If we do the formatting again, the representation shouldn't
+ # change. i.e. the only lossy change should be the original
+ # (whitespace and some semantics aren't preserved).
+ reformatted_again = new_statements.to_source()
+ self.assertEqual(reformatted, reformatted_again,
+ '%s has lossless reformat.' % filename)
+
+ self.assertEqual(len(statements), len(new_statements))
+
+ for i in xrange(0, len(statements)):
+ original = statements[i]
+ formatted = new_statements[i]
+
+ self.assertEqual(original, formatted, '%s %d: %s != %s' % (filename,
+ i, original, formatted))
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.DEBUG)
+ unittest.main()
diff --git a/build/pymake/tests/func-refs.mk b/build/pymake/tests/func-refs.mk
new file mode 100644
index 000000000..82ab17ba8
--- /dev/null
+++ b/build/pymake/tests/func-refs.mk
@@ -0,0 +1,11 @@
+unknown var = uval
+
+all:
+ test "$(subst a,b,value)" = "vblue"
+ test "${subst a,b,va)lue}" = "vb)lue"
+ test "$(subst /,\,ab/c)" = "ab\c"
+ test '$(subst a,b,\\#)' = '\\#'
+ test "$( subst a,b,value)" = ""
+ test "$(Subst a,b,value)" = ""
+ test "$(unknown var)" = "uval"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/functions.mk b/build/pymake/tests/functions.mk
new file mode 100644
index 000000000..817be07aa
--- /dev/null
+++ b/build/pymake/tests/functions.mk
@@ -0,0 +1,36 @@
+all:
+ test "$(subst e,EE,hello)" = "hEEllo"
+ test "$(strip $(NULL) test data )" = "test data"
+ test "$(findstring hell,hello)" = "hell"
+ test "$(findstring heaven,hello)" = ""
+ test "$(filter foo/%.c b%,foo/a.c b.c foo/a.o)" = "foo/a.c b.c"
+ test "$(filter foo,foo bar)" = "foo"
+ test "$(filter-out foo/%.c b%,foo/a.c b.c foo/a.o)" = "foo/a.o"
+ test "$(filter-out %.c,foo,bar.c foo,bar.o)" = "foo,bar.o"
+ test "$(sort .go a b aa A c cc)" = ".go A a aa b c cc"
+ test "$(word 1, hello )" = "hello"
+ test "$(word 2, hello )" = ""
+ test "$(wordlist 1, 2, foo bar baz )" = "foo bar"
+ test "$(words 1 2 3)" = "3"
+ test "$(words )" = "0"
+ test "$(firstword $(NULL) foo bar baz)" = "foo"
+ test "$(firstword )" = ""
+ test "$(dir foo.c path/foo.o dir/dir2/)" = "./ path/ dir/dir2/"
+ test "$(notdir foo.c path/foo.o dir/dir2/)" = "foo.c foo.o "
+ test "$(suffix src/foo.c dir/my.dir/foo foo.o)" = ".c .o"
+ test "$(basename src/foo.c dir/my.dir/foo foo.c .c)" = "src/foo dir/my.dir/foo foo "
+ test "$(addprefix src/,foo bar.c dir/foo)" = "src/foo src/bar.c src/dir/foo"
+ test "$(addsuffix .c,foo dir/bar)" = "foo.c dir/bar.c"
+ test "$(join a b c, 1 2 3)" = "a1 b2 c3"
+ test "$(join a b, 1 2 3)" = "a1 b2 3"
+ test "$(join a b c, 1 2)" = "a1 b2 c"
+ test "$(if $(NULL) ,yes)" = ""
+ test "$(if 1,yes,no)" = "yes"
+ test "$(if ,yes,no )" = "no "
+ test "$(if ,$(error Short-circuit problem))" = ""
+ test "$(or $(NULL),1)" = "1"
+ test "$(or $(NULL),2,$(warning TEST-FAIL bad or short-circuit))" = "2"
+ test "$(and ,$(warning TEST-FAIL bad and short-circuit))" = ""
+ test "$(and 1,2)" = "2"
+ test "$(foreach i,foo bar,found:$(i))" = "found:foo found:bar"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/functiontests.py b/build/pymake/tests/functiontests.py
new file mode 100644
index 000000000..43a344a05
--- /dev/null
+++ b/build/pymake/tests/functiontests.py
@@ -0,0 +1,54 @@
+import unittest
+
+import pymake.data
+import pymake.functions
+
+class VariableRefTest(unittest.TestCase):
+ def test_get_expansions(self):
+ e = pymake.data.StringExpansion('FOO', None)
+ f = pymake.functions.VariableRef(None, e)
+
+ exps = list(f.expansions())
+ self.assertEqual(len(exps), 1)
+
+class GetExpansionsTest(unittest.TestCase):
+ def test_get_arguments(self):
+ f = pymake.functions.SubstFunction(None)
+
+ e1 = pymake.data.StringExpansion('FOO', None)
+ e2 = pymake.data.StringExpansion('BAR', None)
+ e3 = pymake.data.StringExpansion('BAZ', None)
+
+ f.append(e1)
+ f.append(e2)
+ f.append(e3)
+
+ exps = list(f.expansions())
+ self.assertEqual(len(exps), 3)
+
+ def test_descend(self):
+ f = pymake.functions.StripFunction(None)
+
+ e = pymake.data.Expansion(None)
+
+ e1 = pymake.data.StringExpansion('FOO', None)
+ f1 = pymake.functions.VariableRef(None, e1)
+ e.appendfunc(f1)
+
+ f2 = pymake.functions.WildcardFunction(None)
+ e2 = pymake.data.StringExpansion('foo/*', None)
+ f2.append(e2)
+ e.appendfunc(f2)
+
+ f.append(e)
+
+ exps = list(f.expansions())
+ self.assertEqual(len(exps), 1)
+
+ exps = list(f.expansions(True))
+ self.assertEqual(len(exps), 3)
+
+ self.assertFalse(f.is_filesystem_dependent)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/pymake/tests/if-syntaxerr.mk b/build/pymake/tests/if-syntaxerr.mk
new file mode 100644
index 000000000..c172492ef
--- /dev/null
+++ b/build/pymake/tests/if-syntaxerr.mk
@@ -0,0 +1,6 @@
+#T returncode: 2
+
+ifeq ($(FOO,VAR))
+all:
+ @echo TEST_FAIL
+endif
diff --git a/build/pymake/tests/ifdefs-nesting.mk b/build/pymake/tests/ifdefs-nesting.mk
new file mode 100644
index 000000000..340530ffa
--- /dev/null
+++ b/build/pymake/tests/ifdefs-nesting.mk
@@ -0,0 +1,13 @@
+ifdef RANDOM
+ifeq (,$(error Not evaluated!))
+endif
+endif
+
+ifdef RANDOM
+ifeq (,)
+else ifeq (,$(error Not evaluated!))
+endif
+endif
+
+all:
+ @echo TEST-PASS
diff --git a/build/pymake/tests/ifdefs.mk b/build/pymake/tests/ifdefs.mk
new file mode 100644
index 000000000..a779d197b
--- /dev/null
+++ b/build/pymake/tests/ifdefs.mk
@@ -0,0 +1,127 @@
+ifdef FOO
+$(error FOO is not defined!)
+endif
+
+FOO = foo
+FOOFOUND = false
+BARFOUND = false
+BAZFOUND = false
+
+ifdef FOO
+FOOFOUND = true
+else ifdef BAR
+BARFOUND = true
+else
+BAZFOUND = true
+endif
+
+BAR2 = bar2
+FOO2FOUND = false
+BAR2FOUND = false
+BAZ2FOUND = false
+
+ifdef FOO2
+FOO2FOUND = true
+else ifdef BAR2
+BAR2FOUND = true
+else
+BAZ2FOUND = true
+endif
+
+FOO3FOUND = false
+BAR3FOUND = false
+BAZ3FOUND = false
+
+ifdef FOO3
+FOO3FOUND = true
+else ifdef BAR3
+BAR3FOUND = true
+else
+BAZ3FOUND = true
+endif
+
+ifdef RANDOM
+CONTINUATION = \
+else \
+endif
+endif
+
+ifndef ASDFJK
+else
+$(error ASFDJK was not set)
+endif
+
+TESTSET =
+
+ifdef TESTSET
+$(error TESTSET was not set)
+endif
+
+TESTEMPTY = $(NULL)
+ifndef TESTEMPTY
+$(error TEST-FAIL TESTEMPTY was probably expanded!)
+endif
+
+# ifneq ( a,a)
+# $(error Arguments to ifeq should be stripped before evaluation)
+# endif
+
+XSPACE = x # trick
+
+ifneq ($(NULL),$(NULL))
+$(error TEST-FAIL ifneq)
+endif
+
+ifneq (x , x)
+$(error argument-stripping1)
+endif
+
+ifeq ( x,x )
+$(error argument-stripping2)
+endif
+
+ifneq ($(XSPACE), x )
+$(error argument-stripping3)
+endif
+
+ifeq 'x ' ' x'
+$(error TEST-FAIL argument-stripping4)
+endif
+
+all:
+ test $(FOOFOUND) = true # FOOFOUND
+ test $(BARFOUND) = false # BARFOUND
+ test $(BAZFOUND) = false # BAZFOUND
+ test $(FOO2FOUND) = false # FOO2FOUND
+ test $(BAR2FOUND) = true # BAR2FOUND
+ test $(BAZ2FOUND) = false # BAZ2FOUND
+ test $(FOO3FOUND) = false # FOO3FOUND
+ test $(BAR3FOUND) = false # BAR3FOUND
+ test $(BAZ3FOUND) = true # BAZ3FOUND
+ifneq ($(FOO),foo)
+ echo TEST-FAIL 'FOO neq foo: "$(FOO)"'
+endif
+ifneq ($(FOO), foo) # Whitespace after the comma is stripped
+ echo TEST-FAIL 'FOO plus whitespace'
+endif
+ifeq ($(FOO), foo ) # But not trailing whitespace
+ echo TEST-FAIL 'FOO plus trailing whitespace'
+endif
+ifeq ( $(FOO),foo) # Not whitespace after the paren
+ echo TEST-FAIL 'FOO with leading whitespace'
+endif
+ifeq ($(FOO),$(NULL) foo) # Nor whitespace after expansion
+ echo TEST-FAIL 'FOO with embedded ws'
+endif
+ifeq ($(BAR2),bar)
+ echo TEST-FAIL 'BAR2 eq bar'
+endif
+ifeq '$(BAR3FOUND)' 'false'
+ echo BAR3FOUND is ok
+else
+ echo TEST-FAIL BAR3FOUND is not ok
+endif
+ifndef FOO
+ echo TEST-FAIL "foo not defined?"
+endif
+ @echo TEST-PASS
diff --git a/build/pymake/tests/ignore-error.mk b/build/pymake/tests/ignore-error.mk
new file mode 100644
index 000000000..dc8d3a72c
--- /dev/null
+++ b/build/pymake/tests/ignore-error.mk
@@ -0,0 +1,13 @@
+all:
+ -rm foo
+ +-rm bar
+ -+rm baz
+ @-rm bah
+ -@rm humbug
+ +-@rm sincere
+ +@-rm flattery
+ @+-rm will
+ @-+rm not
+ -+@rm save
+ -@+rm you
+ @echo TEST-PASS
diff --git a/build/pymake/tests/implicit-chain.mk b/build/pymake/tests/implicit-chain.mk
new file mode 100644
index 000000000..16288b3f5
--- /dev/null
+++ b/build/pymake/tests/implicit-chain.mk
@@ -0,0 +1,12 @@
+all: test.prog
+ test "$$(cat $<)" = "Program: Object: Source: test.source"
+ @echo TEST-PASS
+
+%.prog: %.object
+ printf "Program: %s" "$$(cat $<)" > $@
+
+%.object: %.source
+ printf "Object: %s" "$$(cat $<)" > $@
+
+%.source:
+ printf "Source: %s" $@ > $@
diff --git a/build/pymake/tests/implicit-dir.mk b/build/pymake/tests/implicit-dir.mk
new file mode 100644
index 000000000..c7f75e8d4
--- /dev/null
+++ b/build/pymake/tests/implicit-dir.mk
@@ -0,0 +1,16 @@
+# Implicit rules have special instructions to deal with directories, so that a pattern rule which doesn't directly apply
+# may still be used.
+
+all: dir/host_test.otest
+
+host_%.otest: %.osource extra.file
+ @echo making $@ from $<
+
+test.osource:
+ @echo TEST-FAIL should have made dir/test.osource
+
+dir/test.osource:
+ @echo TEST-PASS made the correct dependency
+
+extra.file:
+ @echo building $@
diff --git a/build/pymake/tests/implicit-terminal.mk b/build/pymake/tests/implicit-terminal.mk
new file mode 100644
index 000000000..db2e244ed
--- /dev/null
+++ b/build/pymake/tests/implicit-terminal.mk
@@ -0,0 +1,16 @@
+#T returncode: 2
+
+# the %.object rule is "terminal". This means that additional implicit rules cannot be chained to it.
+
+all: test.prog
+ test "$$(cat $<)" = "Program: Object: Source: test.source"
+ @echo TEST-FAIL
+
+%.prog: %.object
+ printf "Program: %s" "$$(cat $<)" > $@
+
+%.object:: %.source
+ printf "Object: %s" "$$(cat $<)" > $@
+
+%.source:
+ printf "Source: %s" $@ > $@
diff --git a/build/pymake/tests/implicitsubdir.mk b/build/pymake/tests/implicitsubdir.mk
new file mode 100644
index 000000000..b9d854a2a
--- /dev/null
+++ b/build/pymake/tests/implicitsubdir.mk
@@ -0,0 +1,12 @@
+$(shell \
+mkdir foo; \
+touch test.in \
+)
+
+all: foo/test.out
+ @echo TEST-PASS
+
+foo/%.out: %.in
+ cp $< $@
+
+
diff --git a/build/pymake/tests/include-dynamic.mk b/build/pymake/tests/include-dynamic.mk
new file mode 100644
index 000000000..571895dc3
--- /dev/null
+++ b/build/pymake/tests/include-dynamic.mk
@@ -0,0 +1,21 @@
+$(shell \
+if ! test -f include-dynamic.inc; then \
+ echo "TESTVAR = oldval" > include-dynamic.inc; \
+ sleep 2; \
+ echo "TESTVAR = newval" > include-dynamic.inc.in; \
+fi \
+)
+
+# before running the 'all' rule, we should be rebuilding include-dynamic.inc,
+# because there is a rule to do so
+
+all:
+ test $(TESTVAR) = newval
+ test "$(MAKE_RESTARTS)" = 1
+ @echo TEST-PASS
+
+include-dynamic.inc: include-dynamic.inc.in
+ test "$(MAKE_RESTARTS)" = ""
+ cp $< $@
+
+include include-dynamic.inc
diff --git a/build/pymake/tests/include-file.inc b/build/pymake/tests/include-file.inc
new file mode 100644
index 000000000..d5d495dec
--- /dev/null
+++ b/build/pymake/tests/include-file.inc
@@ -0,0 +1 @@
+INCLUDED = yes
diff --git a/build/pymake/tests/include-missing.mk b/build/pymake/tests/include-missing.mk
new file mode 100644
index 000000000..583d0a065
--- /dev/null
+++ b/build/pymake/tests/include-missing.mk
@@ -0,0 +1,9 @@
+#T returncode: 2
+
+# If an include file isn't present and doesn't have a rule to remake it, make
+# should fail.
+
+include notfound.mk
+
+all:
+ @echo TEST-FAIL
diff --git a/build/pymake/tests/include-notfound.mk b/build/pymake/tests/include-notfound.mk
new file mode 100644
index 000000000..1ee7e05b2
--- /dev/null
+++ b/build/pymake/tests/include-notfound.mk
@@ -0,0 +1,19 @@
+ifdef __WIN32__
+PS:=\\#
+else
+PS:=/
+endif
+
+ifneq ($(strip $(MAKEFILE_LIST)),$(NATIVE_TESTPATH)$(PS)include-notfound.mk)
+$(error MAKEFILE_LIST incorrect: '$(MAKEFILE_LIST)' (expected '$(NATIVE_TESTPATH)$(PS)include-notfound.mk'))
+endif
+
+-include notfound.inc-dummy
+
+ifneq ($(strip $(MAKEFILE_LIST)),$(NATIVE_TESTPATH)$(PS)include-notfound.mk)
+$(error MAKEFILE_LIST incorrect: '$(MAKEFILE_LIST)' (expected '$(NATIVE_TESTPATH)$(PS)include-notfound.mk'))
+endif
+
+all:
+ @echo TEST-PASS
+
diff --git a/build/pymake/tests/include-optional-warning.mk b/build/pymake/tests/include-optional-warning.mk
new file mode 100644
index 000000000..901938dff
--- /dev/null
+++ b/build/pymake/tests/include-optional-warning.mk
@@ -0,0 +1,4 @@
+-include TEST-FAIL.mk
+
+all:
+ @echo TEST-PASS
diff --git a/build/pymake/tests/include-regen.mk b/build/pymake/tests/include-regen.mk
new file mode 100644
index 000000000..c86e0c78d
--- /dev/null
+++ b/build/pymake/tests/include-regen.mk
@@ -0,0 +1,10 @@
+# avoid infinite loops by not remaking makefiles with
+# double-colon no-dependency rules
+# http://www.gnu.org/software/make/manual/make.html#Remaking-Makefiles
+-include notfound.mk
+
+all:
+ @echo TEST-PASS
+
+notfound.mk::
+ @echo TEST-FAIL
diff --git a/build/pymake/tests/include-regen2.mk b/build/pymake/tests/include-regen2.mk
new file mode 100644
index 000000000..fc7fef073
--- /dev/null
+++ b/build/pymake/tests/include-regen2.mk
@@ -0,0 +1,10 @@
+# make should make makefiles that it has rules for if they are
+# included
+include test.mk
+
+all:
+ test "$(X)" = "1"
+ @echo "TEST-PASS"
+
+test.mk:
+ @echo "X = 1" > $@
diff --git a/build/pymake/tests/include-regen3.mk b/build/pymake/tests/include-regen3.mk
new file mode 100644
index 000000000..878ce0adc
--- /dev/null
+++ b/build/pymake/tests/include-regen3.mk
@@ -0,0 +1,10 @@
+# make should make makefiles that it has rules for if they are
+# included
+-include test.mk
+
+all:
+ test "$(X)" = "1"
+ @echo "TEST-PASS"
+
+test.mk:
+ @echo "X = 1" > $@
diff --git a/build/pymake/tests/include-test.mk b/build/pymake/tests/include-test.mk
new file mode 100644
index 000000000..3608fc269
--- /dev/null
+++ b/build/pymake/tests/include-test.mk
@@ -0,0 +1,8 @@
+$(shell echo "INCLUDED2 = yes" >local-include.inc)
+
+include $(TESTPATH)/include-file.inc local-include.inc
+
+all:
+ test "$(INCLUDED)" = "yes"
+ test "$(INCLUDED2)" = "yes"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/includedeps-norebuild.mk b/build/pymake/tests/includedeps-norebuild.mk
new file mode 100644
index 000000000..e30abd439
--- /dev/null
+++ b/build/pymake/tests/includedeps-norebuild.mk
@@ -0,0 +1,15 @@
+#T gmake skip
+
+$(shell \
+touch filemissing; \
+sleep 2; \
+touch file1; \
+)
+
+all: file1
+ @echo TEST-PASS
+
+includedeps $(TESTPATH)/includedeps.deps
+
+file1:
+ @echo TEST-FAIL
diff --git a/build/pymake/tests/includedeps-sideeffects.mk b/build/pymake/tests/includedeps-sideeffects.mk
new file mode 100644
index 000000000..7e4ea30a2
--- /dev/null
+++ b/build/pymake/tests/includedeps-sideeffects.mk
@@ -0,0 +1,10 @@
+#T gmake skip
+#T returncode: 2
+
+all: file1 filemissing
+ @echo TEST-PASS
+
+includedeps $(TESTPATH)/includedeps.deps
+
+file:
+ touch $@
diff --git a/build/pymake/tests/includedeps-stripdotslash.deps b/build/pymake/tests/includedeps-stripdotslash.deps
new file mode 100644
index 000000000..352fca1bb
--- /dev/null
+++ b/build/pymake/tests/includedeps-stripdotslash.deps
@@ -0,0 +1 @@
+./test: TEST-PASS
diff --git a/build/pymake/tests/includedeps-stripdotslash.mk b/build/pymake/tests/includedeps-stripdotslash.mk
new file mode 100644
index 000000000..ee942e6db
--- /dev/null
+++ b/build/pymake/tests/includedeps-stripdotslash.mk
@@ -0,0 +1,8 @@
+#T gmake skip
+
+test:
+ @echo $<
+
+includedeps $(TESTPATH)/includedeps-stripdotslash.deps
+
+TEST-PASS:
diff --git a/build/pymake/tests/includedeps-variables.deps b/build/pymake/tests/includedeps-variables.deps
new file mode 100644
index 000000000..ba69e9b6c
--- /dev/null
+++ b/build/pymake/tests/includedeps-variables.deps
@@ -0,0 +1 @@
+$(FILE)1: filemissing
diff --git a/build/pymake/tests/includedeps-variables.mk b/build/pymake/tests/includedeps-variables.mk
new file mode 100644
index 000000000..314618da4
--- /dev/null
+++ b/build/pymake/tests/includedeps-variables.mk
@@ -0,0 +1,10 @@
+#T gmake skip
+
+FILE = includedeps-variables
+
+all: $(FILE)1
+
+includedeps $(TESTPATH)/includedeps-variables.deps
+
+filemissing:
+ @echo TEST-PASS
diff --git a/build/pymake/tests/includedeps.deps b/build/pymake/tests/includedeps.deps
new file mode 100644
index 000000000..d3017c078
--- /dev/null
+++ b/build/pymake/tests/includedeps.deps
@@ -0,0 +1 @@
+file1: filemissing
diff --git a/build/pymake/tests/includedeps.mk b/build/pymake/tests/includedeps.mk
new file mode 100644
index 000000000..deaa71fe8
--- /dev/null
+++ b/build/pymake/tests/includedeps.mk
@@ -0,0 +1,9 @@
+#T gmake skip
+
+all: file1
+ @echo TEST-PASS
+
+includedeps $(TESTPATH)/includedeps.deps
+
+file1:
+ touch $@
diff --git a/build/pymake/tests/info.mk b/build/pymake/tests/info.mk
new file mode 100644
index 000000000..8dddfd815
--- /dev/null
+++ b/build/pymake/tests/info.mk
@@ -0,0 +1,8 @@
+#T grep-for: "info-printed\ninfo-nth"
+all:
+
+INFO = info-printed
+
+$(info $(INFO))
+$(info $(subst second,nth,info-second))
+$(info TEST-PASS)
diff --git a/build/pymake/tests/justprint-native.mk b/build/pymake/tests/justprint-native.mk
new file mode 100644
index 000000000..580e402e9
--- /dev/null
+++ b/build/pymake/tests/justprint-native.mk
@@ -0,0 +1,28 @@
+## $(TOUCH) and $(RM) are native commands in pymake.
+## Test that pymake --just-print just prints them.
+
+ifndef TOUCH
+TOUCH = touch
+endif
+
+all:
+ $(RM) justprint-native-file1.txt
+ $(TOUCH) justprint-native-file2.txt
+ $(MAKE) --just-print -f $(TESTPATH)/justprint-native.mk justprint_target > justprint.log
+# make --just-print shouldn't have actually done anything.
+ test ! -f justprint-native-file1.txt
+ test -f justprint-native-file2.txt
+# but it should have printed each command
+ grep -q 'touch justprint-native-file1.txt' justprint.log
+ grep -q 'rm -f justprint-native-file2.txt' justprint.log
+ grep -q 'this string is "unlikely to appear in the log by chance"' justprint.log
+# tidy up
+ $(RM) justprint-native-file2.txt
+ @echo TEST-PASS
+
+justprint_target:
+ $(TOUCH) justprint-native-file1.txt
+ $(RM) justprint-native-file2.txt
+ this string is "unlikely to appear in the log by chance"
+
+.PHONY: justprint_target
diff --git a/build/pymake/tests/justprint.mk b/build/pymake/tests/justprint.mk
new file mode 100644
index 000000000..be11ba8de
--- /dev/null
+++ b/build/pymake/tests/justprint.mk
@@ -0,0 +1,5 @@
+#T commandline: ['-n']
+
+all:
+ false # without -n, we wouldn't get past this
+ TEST-PASS # heh
diff --git a/build/pymake/tests/keep-going-doublecolon.mk b/build/pymake/tests/keep-going-doublecolon.mk
new file mode 100644
index 000000000..fa5b31df8
--- /dev/null
+++ b/build/pymake/tests/keep-going-doublecolon.mk
@@ -0,0 +1,16 @@
+#T commandline: ['-k']
+#T returncode: 2
+#T grep-for: "TEST-PASS"
+
+all:: t1
+ @echo TEST-FAIL "(t1)"
+
+all:: t2
+ @echo TEST-PASS
+
+t1:
+ @false
+
+t2:
+ touch $@
+
diff --git a/build/pymake/tests/keep-going-parallel.mk b/build/pymake/tests/keep-going-parallel.mk
new file mode 100644
index 000000000..a91d1a6ed
--- /dev/null
+++ b/build/pymake/tests/keep-going-parallel.mk
@@ -0,0 +1,11 @@
+#T commandline: ['-k', '-j2']
+#T returncode: 2
+#T grep-for: "TEST-PASS"
+
+all: t1 slow1 slow2 slow3 t2
+
+t2:
+ @echo TEST-PASS
+
+slow%:
+ sleep 1
diff --git a/build/pymake/tests/keep-going.mk b/build/pymake/tests/keep-going.mk
new file mode 100644
index 000000000..4c709288c
--- /dev/null
+++ b/build/pymake/tests/keep-going.mk
@@ -0,0 +1,14 @@
+#T commandline: ['-k']
+#T returncode: 2
+#T grep-for: "TEST-PASS"
+
+all: t2 t3
+
+t1:
+ @false
+
+t2: t1
+ @echo TEST-FAIL
+
+t3:
+ @echo TEST-PASS
diff --git a/build/pymake/tests/line-continuations.mk b/build/pymake/tests/line-continuations.mk
new file mode 100644
index 000000000..8b44480ea
--- /dev/null
+++ b/build/pymake/tests/line-continuations.mk
@@ -0,0 +1,24 @@
+VAR = val1 \
+ val2
+
+VAR2 = val1space\
+val2
+
+VAR3 = val3 \\\
+ cont3
+
+all: otarget test.target
+ test "$(VAR)" = "val1 val2 "
+ test "$(VAR2)" = "val1space val2"
+ test '$(VAR3)' = 'val3 \ cont3'
+ test "hello \
+ world" = "hello world"
+ test "hello" = \
+"hello"
+ @echo TEST-PASS
+
+otarget: ; test "hello\
+ world" = "helloworld"
+
+test.target: %.target: ; test "hello\
+ world" = "helloworld"
diff --git a/build/pymake/tests/link-search.mk b/build/pymake/tests/link-search.mk
new file mode 100644
index 000000000..ea827f391
--- /dev/null
+++ b/build/pymake/tests/link-search.mk
@@ -0,0 +1,7 @@
+$(shell \
+touch libfoo.so \
+)
+
+all: -lfoo
+ test "$<" = "libfoo.so"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/makeflags.mk b/build/pymake/tests/makeflags.mk
new file mode 100644
index 000000000..288ff7866
--- /dev/null
+++ b/build/pymake/tests/makeflags.mk
@@ -0,0 +1,7 @@
+#T environment: {'MAKEFLAGS': 'OVAR=oval'}
+
+all:
+ test "$(OVAR)" = "oval"
+ test "$$OVAR" = "oval"
+ @echo TEST-PASS
+
diff --git a/build/pymake/tests/matchany.mk b/build/pymake/tests/matchany.mk
new file mode 100644
index 000000000..7876c90a3
--- /dev/null
+++ b/build/pymake/tests/matchany.mk
@@ -0,0 +1,14 @@
+#T returncode: 2
+
+# we should fail to make foo.ooo from foo.ooo.test
+all: foo.ooo
+ @echo TEST-FAIL
+
+%.ooo:
+
+# this match-anything pattern should not apply to %.ooo
+%: %.test
+ cp $< $@
+
+foo.ooo.test:
+ touch $@
diff --git a/build/pymake/tests/matchany2.mk b/build/pymake/tests/matchany2.mk
new file mode 100644
index 000000000..d21d9702c
--- /dev/null
+++ b/build/pymake/tests/matchany2.mk
@@ -0,0 +1,13 @@
+# we should succeed in making foo.ooo from foo.ooo.test
+all: foo.ooo
+ @echo TEST-PASS
+
+%.ooo: %.ccc
+ exit 1
+
+# this match-anything rule is terminal, and therefore applies
+%:: %.test
+ cp $< $@
+
+foo.ooo.test:
+ touch $@
diff --git a/build/pymake/tests/matchany3.mk b/build/pymake/tests/matchany3.mk
new file mode 100644
index 000000000..83de8af2b
--- /dev/null
+++ b/build/pymake/tests/matchany3.mk
@@ -0,0 +1,10 @@
+$(shell \
+echo "target" > target.in; \
+)
+
+all: target
+ test "$$(cat $^)" = "target"
+ @echo TEST-PASS
+
+%: %.in
+ cp $< $@
diff --git a/build/pymake/tests/mkdir-fail.mk b/build/pymake/tests/mkdir-fail.mk
new file mode 100644
index 000000000..b05734aa9
--- /dev/null
+++ b/build/pymake/tests/mkdir-fail.mk
@@ -0,0 +1,7 @@
+#T returncode: 2
+all:
+ mkdir newdir/subdir
+ test ! -d newdir/subdir
+ test ! -d newdir
+ rm -r newdir
+ @echo TEST-PASS
diff --git a/build/pymake/tests/mkdir.mk b/build/pymake/tests/mkdir.mk
new file mode 100644
index 000000000..413348f77
--- /dev/null
+++ b/build/pymake/tests/mkdir.mk
@@ -0,0 +1,27 @@
+MKDIR ?= mkdir
+
+all:
+ $(MKDIR) newdir
+ test -d newdir
+ # subdir, parent exists
+ $(MKDIR) newdir/subdir
+ test -d newdir/subdir
+ # -p, existing dir
+ $(MKDIR) -p newdir
+ # -p, existing subdir
+ $(MKDIR) -p newdir/subdir
+ # multiple subdirs, existing parent
+ $(MKDIR) newdir/subdir1 newdir/subdir2
+ test -d newdir/subdir1 -a -d newdir/subdir2
+ rm -r newdir
+ # -p, subdir, no existing parent
+ $(MKDIR) -p newdir/subdir
+ test -d newdir/subdir
+ rm -r newdir
+ # -p, multiple subdirs, no existing parent
+ $(MKDIR) -p newdir/subdir1 newdir/subdir2
+ test -d newdir/subdir1 -a -d newdir/subdir2
+ # -p, multiple existing subdirs
+ $(MKDIR) -p newdir/subdir1 newdir/subdir2
+ rm -r newdir
+ @echo TEST-PASS
diff --git a/build/pymake/tests/multiple-rules-prerequisite-merge.mk b/build/pymake/tests/multiple-rules-prerequisite-merge.mk
new file mode 100644
index 000000000..480d3b58c
--- /dev/null
+++ b/build/pymake/tests/multiple-rules-prerequisite-merge.mk
@@ -0,0 +1,25 @@
+# When a target is defined multiple times, the prerequisites should get
+# merged.
+
+default: foo bar baz
+
+foo:
+ test "$<" = "foo.in1"
+ @echo TEST-PASS
+
+foo: foo.in1
+
+bar: bar.in1
+ test "$<" = "bar.in1"
+ test "$^" = "bar.in1 bar.in2"
+ @echo TEST-PASS
+
+bar: bar.in2
+
+baz: baz.in2
+baz: baz.in1
+ test "$<" = "baz.in1"
+ test "$^" = "baz.in1 baz.in2"
+ @echo TEST-PASS
+
+foo.in1 bar.in1 bar.in2 baz.in1 baz.in2:
diff --git a/build/pymake/tests/native-command-delay-load.mk b/build/pymake/tests/native-command-delay-load.mk
new file mode 100644
index 000000000..a9f3774eb
--- /dev/null
+++ b/build/pymake/tests/native-command-delay-load.mk
@@ -0,0 +1,12 @@
+#T gmake skip
+
+# This test exists to verify that sys.path is adjusted during command
+# execution and that delay importing a module will work.
+
+CMD = %pycmd delayloadfn
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+ $(CMD)
+ @echo TEST-PASS
+
diff --git a/build/pymake/tests/native-command-raise.mk b/build/pymake/tests/native-command-raise.mk
new file mode 100644
index 000000000..d1b28b331
--- /dev/null
+++ b/build/pymake/tests/native-command-raise.mk
@@ -0,0 +1,9 @@
+#T gmake skip
+#T returncode: 2
+#T grep-for: "Exception: info-exception"
+
+CMD = %pycmd asplode_raise
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+ @$(CMD) info-exception
diff --git a/build/pymake/tests/native-command-return-fail1.mk b/build/pymake/tests/native-command-return-fail1.mk
new file mode 100644
index 000000000..0cf085ae2
--- /dev/null
+++ b/build/pymake/tests/native-command-return-fail1.mk
@@ -0,0 +1,8 @@
+#T gmake skip
+#T returncode: 2
+
+CMD = %pycmd asplode_return
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+ $(CMD) 1
diff --git a/build/pymake/tests/native-command-return-fail2.mk b/build/pymake/tests/native-command-return-fail2.mk
new file mode 100644
index 000000000..c071fc879
--- /dev/null
+++ b/build/pymake/tests/native-command-return-fail2.mk
@@ -0,0 +1,8 @@
+#T gmake skip
+#T returncode: 2
+
+CMD = %pycmd asplode_return
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+ $(CMD) not-an-integer
diff --git a/build/pymake/tests/native-command-return.mk b/build/pymake/tests/native-command-return.mk
new file mode 100644
index 000000000..3e4d2e0c4
--- /dev/null
+++ b/build/pymake/tests/native-command-return.mk
@@ -0,0 +1,11 @@
+#T gmake skip
+
+CMD = %pycmd asplode_return
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+ $(CMD) 0
+ -$(CMD) 1
+ $(CMD) None
+ -$(CMD) not-an-integer
+ @echo TEST-PASS
diff --git a/build/pymake/tests/native-command-shell-glob.mk b/build/pymake/tests/native-command-shell-glob.mk
new file mode 100644
index 000000000..4bcdad8b9
--- /dev/null
+++ b/build/pymake/tests/native-command-shell-glob.mk
@@ -0,0 +1,11 @@
+#T gmake skip
+all:
+ mkdir shell-glob-test
+ touch shell-glob-test/foo.txt
+ touch shell-glob-test/bar.txt
+ touch shell-glob-test/a.foo
+ touch shell-glob-test/b.foo
+ $(RM) shell-glob-test/*.txt
+ $(RM) shell-glob-test/?.foo
+ rmdir shell-glob-test
+ @echo TEST-PASS
diff --git a/build/pymake/tests/native-command-sys-exit-fail1.mk b/build/pymake/tests/native-command-sys-exit-fail1.mk
new file mode 100644
index 000000000..8e74800ed
--- /dev/null
+++ b/build/pymake/tests/native-command-sys-exit-fail1.mk
@@ -0,0 +1,8 @@
+#T gmake skip
+#T returncode: 2
+
+CMD = %pycmd asplode
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+ $(CMD) 1
diff --git a/build/pymake/tests/native-command-sys-exit-fail2.mk b/build/pymake/tests/native-command-sys-exit-fail2.mk
new file mode 100644
index 000000000..0a04395ad
--- /dev/null
+++ b/build/pymake/tests/native-command-sys-exit-fail2.mk
@@ -0,0 +1,8 @@
+#T gmake skip
+#T returncode: 2
+
+CMD = %pycmd asplode
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+ $(CMD) not-an-integer
diff --git a/build/pymake/tests/native-command-sys-exit.mk b/build/pymake/tests/native-command-sys-exit.mk
new file mode 100644
index 000000000..c04913aca
--- /dev/null
+++ b/build/pymake/tests/native-command-sys-exit.mk
@@ -0,0 +1,11 @@
+#T gmake skip
+
+CMD = %pycmd asplode
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+ $(CMD) 0
+ -$(CMD) 1
+ $(CMD) None
+ -$(CMD) not-an-integer
+ @echo TEST-PASS
diff --git a/build/pymake/tests/native-environment.mk b/build/pymake/tests/native-environment.mk
new file mode 100644
index 000000000..36bd5894a
--- /dev/null
+++ b/build/pymake/tests/native-environment.mk
@@ -0,0 +1,11 @@
+#T gmake skip
+export EXPECTED := some data
+
+PYCOMMANDPATH = $(TESTPATH)
+
+all:
+ %pycmd writeenvtofile results EXPECTED
+ test "$$(cat results)" = "$(EXPECTED)"
+ %pycmd writesubprocessenvtofile results EXPECTED
+ test "$$(cat results)" = "$(EXPECTED)"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/native-pycommandpath-sep.mk b/build/pymake/tests/native-pycommandpath-sep.mk
new file mode 100644
index 000000000..b1c2c2b97
--- /dev/null
+++ b/build/pymake/tests/native-pycommandpath-sep.mk
@@ -0,0 +1,21 @@
+#T gmake skip
+EXPECTED := some data
+
+# verify that we can load native command modules from
+# multiple directories in PYCOMMANDPATH separated by the native
+# path separator
+ifdef __WIN32__
+PS:=;
+else
+PS:=:
+endif
+CMD = %pycmd writetofile
+CMD2 = %pymod writetofile
+PYCOMMANDPATH = $(TESTPATH)$(PS)$(TESTPATH)/subdir
+
+all:
+ $(CMD) results $(EXPECTED)
+ test "$$(cat results)" = "$(EXPECTED)"
+ $(CMD2) results2 $(EXPECTED)
+ test "$$(cat results2)" = "$(EXPECTED)"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/native-pycommandpath.mk b/build/pymake/tests/native-pycommandpath.mk
new file mode 100644
index 000000000..dd0fbc9f9
--- /dev/null
+++ b/build/pymake/tests/native-pycommandpath.mk
@@ -0,0 +1,15 @@
+#T gmake skip
+EXPECTED := some data
+
+# verify that we can load native command modules from
+# multiple space-separated directories in PYCOMMANDPATH
+CMD = %pycmd writetofile
+CMD2 = %pymod writetofile
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+ $(CMD) results $(EXPECTED)
+ test "$$(cat results)" = "$(EXPECTED)"
+ $(CMD2) results2 $(EXPECTED)
+ test "$$(cat results2)" = "$(EXPECTED)"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/native-simple.mk b/build/pymake/tests/native-simple.mk
new file mode 100644
index 000000000..626a58670
--- /dev/null
+++ b/build/pymake/tests/native-simple.mk
@@ -0,0 +1,12 @@
+ifndef TOUCH
+TOUCH = touch
+endif
+
+all: testfile {testfile2} (testfile3)
+ test -f testfile
+ test -f {testfile2}
+ test -f "(testfile3)"
+ @echo TEST-PASS
+
+testfile {testfile2} (testfile3):
+ $(TOUCH) "$@"
diff --git a/build/pymake/tests/native-touch.mk b/build/pymake/tests/native-touch.mk
new file mode 100644
index 000000000..811161ece
--- /dev/null
+++ b/build/pymake/tests/native-touch.mk
@@ -0,0 +1,15 @@
+TOUCH ?= touch
+
+foo:
+ $(TOUCH) bar
+ $(TOUCH) baz
+ $(MAKE) -f $(TESTPATH)/native-touch.mk baz
+ $(TOUCH) -t 198007040802 baz
+ $(MAKE) -f $(TESTPATH)/native-touch.mk baz
+
+bar:
+ $(TOUCH) $@
+
+baz: bar
+ echo TEST-PASS
+ $(TOUCH) $@
diff --git a/build/pymake/tests/newlines.mk b/build/pymake/tests/newlines.mk
new file mode 100644
index 000000000..5d8195c94
--- /dev/null
+++ b/build/pymake/tests/newlines.mk
@@ -0,0 +1,30 @@
+#T gmake skip
+
+# Test that we handle \\\n properly
+
+all: dep1 dep2 dep3
+ cat testfile
+ test `cat testfile` = "data";
+ test "$$(cat results)" = "$(EXPECTED)";
+ @echo TEST-PASS
+
+# Test that something that still needs to go to the shell works
+testfile:
+ printf "data" \
+ >>$@
+
+dep1: testfile
+
+# Test that something that does not need to go to the shell works
+dep2:
+ $(echo foo) \
+ $(echo bar)
+
+export EXPECTED := some data
+
+CMD = %pycmd writeenvtofile
+PYCOMMANDPATH = $(TESTPATH)
+
+dep3:
+ $(CMD) \
+ results EXPECTED
diff --git a/build/pymake/tests/no-remake.mk b/build/pymake/tests/no-remake.mk
new file mode 100644
index 000000000..c8df81bc3
--- /dev/null
+++ b/build/pymake/tests/no-remake.mk
@@ -0,0 +1,7 @@
+$(shell date >testfile)
+
+all: testfile
+ @echo TEST-PASS
+
+testfile:
+ @echo TEST-FAIL "We shouldn't have remade this!"
diff --git a/build/pymake/tests/nosuchfile.mk b/build/pymake/tests/nosuchfile.mk
new file mode 100644
index 000000000..cca9ce1e9
--- /dev/null
+++ b/build/pymake/tests/nosuchfile.mk
@@ -0,0 +1,4 @@
+#T returncode: 2
+
+all:
+ reallythereisnosuchcommand
diff --git a/build/pymake/tests/notargets.mk b/build/pymake/tests/notargets.mk
new file mode 100644
index 000000000..8e55d944f
--- /dev/null
+++ b/build/pymake/tests/notargets.mk
@@ -0,0 +1,5 @@
+$(NULL): foo.c
+ @echo TEST-FAIL
+
+all:
+ @echo TEST-PASS
diff --git a/build/pymake/tests/notparallel.mk b/build/pymake/tests/notparallel.mk
new file mode 100644
index 000000000..4fd8b1a8d
--- /dev/null
+++ b/build/pymake/tests/notparallel.mk
@@ -0,0 +1,8 @@
+#T commandline: ['-j3']
+
+include $(TESTPATH)/serial-rule-execution.mk
+
+all::
+ $(MAKE) -f $(TESTPATH)/parallel-simple.mk
+
+.NOTPARALLEL:
diff --git a/build/pymake/tests/oneline-command-continuations.mk b/build/pymake/tests/oneline-command-continuations.mk
new file mode 100644
index 000000000..c11f3df52
--- /dev/null
+++ b/build/pymake/tests/oneline-command-continuations.mk
@@ -0,0 +1,5 @@
+all: test
+ @echo TEST-PASS
+
+test: ; test "Hello \
+ world" = "Hello world"
diff --git a/build/pymake/tests/override-propagate.mk b/build/pymake/tests/override-propagate.mk
new file mode 100644
index 000000000..a1663ff41
--- /dev/null
+++ b/build/pymake/tests/override-propagate.mk
@@ -0,0 +1,37 @@
+#T commandline: ['-w', 'OVAR=oval']
+
+OVAR=mval
+
+all: vartest run-override
+ $(MAKE) -f $(TESTPATH)/override-propagate.mk vartest
+ @echo TEST-PASS
+
+CLINE := OVAR=oval TESTPATH=$(TESTPATH) NATIVE_TESTPATH=$(NATIVE_TESTPATH)
+ifdef __WIN32__
+CLINE += __WIN32__=1
+endif
+
+SORTED_CLINE := $(subst \,\\,$(sort $(CLINE)))
+
+vartest:
+ @echo MAKELEVEL: '$(MAKELEVEL)'
+ test '$(value MAKEFLAGS)' = 'w -- $$(MAKEOVERRIDES)'
+ test '$(origin MAKEFLAGS)' = 'file'
+ test '$(value MAKEOVERRIDES)' = '$${-*-command-variables-*-}'
+ test "$(sort $(MAKEOVERRIDES))" = "$(SORTED_CLINE)"
+ test '$(origin MAKEOVERRIDES)' = 'environment'
+ test '$(origin -*-command-variables-*-)' = 'automatic'
+ test "$(origin OVAR)" = "command line"
+ test "$(OVAR)" = "oval"
+
+run-override: MAKEOVERRIDES=
+run-override:
+ test "$(OVAR)" = "oval"
+ $(MAKE) -f $(TESTPATH)/override-propagate.mk otest
+
+otest:
+ test '$(value MAKEFLAGS)' = 'w'
+ test '$(value MAKEOVERRIDES)' = '$${-*-command-variables-*-}'
+ test '$(MAKEOVERRIDES)' = ''
+ test '$(origin -*-command-variables-*-)' = 'undefined'
+ test "$(OVAR)" = "mval"
diff --git a/build/pymake/tests/parallel-dep-resolution.mk b/build/pymake/tests/parallel-dep-resolution.mk
new file mode 100644
index 000000000..7967eba2d
--- /dev/null
+++ b/build/pymake/tests/parallel-dep-resolution.mk
@@ -0,0 +1,8 @@
+#T commandline: ['-j3']
+#T returncode: 2
+
+all: t1 t2
+
+t1:
+ sleep 1
+ touch t1 t2
diff --git a/build/pymake/tests/parallel-dep-resolution2.mk b/build/pymake/tests/parallel-dep-resolution2.mk
new file mode 100644
index 000000000..7d61e6b3e
--- /dev/null
+++ b/build/pymake/tests/parallel-dep-resolution2.mk
@@ -0,0 +1,9 @@
+#T commandline: ['-j3']
+#T returncode: 2
+
+all::
+ sleep 1
+ touch somefile
+
+all:: somefile
+ @echo TEST-PASS
diff --git a/build/pymake/tests/parallel-native.mk b/build/pymake/tests/parallel-native.mk
new file mode 100644
index 000000000..d50cfbdbb
--- /dev/null
+++ b/build/pymake/tests/parallel-native.mk
@@ -0,0 +1,21 @@
+#T commandline: ['-j2']
+
+# ensure that calling python commands doesn't block other targets
+ifndef SLEEP
+SLEEP := sleep
+endif
+
+PRINTF = printf "$@:0:" >>results
+EXPECTED = target2:0:target1:0:
+
+all:: target1 target2
+ cat results
+ test "$$(cat results)" = "$(EXPECTED)"
+ @echo TEST-PASS
+
+target1:
+ $(SLEEP) 0.1
+ $(PRINTF)
+
+target2:
+ $(PRINTF)
diff --git a/build/pymake/tests/parallel-simple.mk b/build/pymake/tests/parallel-simple.mk
new file mode 100644
index 000000000..f1aafc5f1
--- /dev/null
+++ b/build/pymake/tests/parallel-simple.mk
@@ -0,0 +1,27 @@
+#T commandline: ['-j2']
+
+# CAUTION: this makefile is also used by serial-toparallel.mk
+
+define SLOWMAKE
+printf "$@:0:" >>results
+sleep 0.5
+printf "$@:1:" >>results
+sleep 0.5
+printf "$@:2:" >>results
+endef
+
+EXPECTED = target1:0:target2:0:target1:1:target2:1:target1:2:target2:2:
+
+all:: target1 target2
+ cat results
+ test "$$(cat results)" = "$(EXPECTED)"
+ @echo TEST-PASS
+
+target1:
+ $(SLOWMAKE)
+
+target2:
+ sleep 0.1
+ $(SLOWMAKE)
+
+.PHONY: all
diff --git a/build/pymake/tests/parallel-submake.mk b/build/pymake/tests/parallel-submake.mk
new file mode 100644
index 000000000..65cb2cf7c
--- /dev/null
+++ b/build/pymake/tests/parallel-submake.mk
@@ -0,0 +1,17 @@
+#T commandline: ['-j2']
+
+# A submake shouldn't return control to the parent until it has actually finished doing everything.
+
+all:
+ -$(MAKE) -f $(TESTPATH)/parallel-submake.mk subtarget
+ cat results
+ test "$$(cat results)" = "0123"
+ @echo TEST-PASS
+
+subtarget: succeed-slowly fail-quickly
+
+succeed-slowly:
+ printf 0 >>results; sleep 1; printf 1 >>results; sleep 1; printf 2 >>results; sleep 1; printf 3 >>results
+
+fail-quickly:
+ exit 1
diff --git a/build/pymake/tests/parallel-toserial.mk b/build/pymake/tests/parallel-toserial.mk
new file mode 100644
index 000000000..9a355eb33
--- /dev/null
+++ b/build/pymake/tests/parallel-toserial.mk
@@ -0,0 +1,31 @@
+#T commandline: ['-j4']
+
+# Test that -j1 in a submake has the proper effect.
+
+define SLOWCMD
+printf "$@:0:" >>$(RFILE)
+sleep 0.5
+printf "$@:1:" >>$(RFILE)
+endef
+
+all: p1 p2
+subtarget: s1 s2
+
+p1 p2: RFILE = presult
+s1 s2: RFILE = sresult
+
+p1 s1:
+ $(SLOWCMD)
+
+p2 s2:
+ sleep 0.1
+ $(SLOWCMD)
+
+all:
+ $(MAKE) -j1 -f $(TESTPATH)/parallel-toserial.mk subtarget
+ printf "presult: %s\n" "$$(cat presult)"
+ test "$$(cat presult)" = "p1:0:p2:0:p1:1:p2:1:"
+ printf "sresult: %s\n" "$$(cat sresult)"
+ test "$$(cat sresult)" = "s1:0:s1:1:s2:0:s2:1:"
+ @echo TEST-PASS
+
diff --git a/build/pymake/tests/parallel-waiting.mk b/build/pymake/tests/parallel-waiting.mk
new file mode 100644
index 000000000..40a6e0d50
--- /dev/null
+++ b/build/pymake/tests/parallel-waiting.mk
@@ -0,0 +1,21 @@
+#T commandline: ['-j2']
+
+EXPECTED = target1:before:target2:1:target2:2:target2:3:target1:after
+
+all:: target1 target2
+ cat results
+ test "$$(cat results)" = "$(EXPECTED)"
+ @echo TEST-PASS
+
+target1:
+ printf "$@:before:" >>results
+ sleep 4
+ printf "$@:after" >>results
+
+target2:
+ sleep 0.2
+ printf "$@:1:" >>results
+ sleep 0.1
+ printf "$@:2:" >>results
+ sleep 0.1
+ printf "$@:3:" >>results
diff --git a/build/pymake/tests/parentheses.mk b/build/pymake/tests/parentheses.mk
new file mode 100644
index 000000000..f207234ff
--- /dev/null
+++ b/build/pymake/tests/parentheses.mk
@@ -0,0 +1,2 @@
+all:
+ @(echo TEST-PASS)
diff --git a/build/pymake/tests/parsertests.py b/build/pymake/tests/parsertests.py
new file mode 100644
index 000000000..ab6406be0
--- /dev/null
+++ b/build/pymake/tests/parsertests.py
@@ -0,0 +1,314 @@
+import pymake.data, pymake.parser, pymake.parserdata, pymake.functions
+import unittest
+import logging
+
+from cStringIO import StringIO
+
+def multitest(cls):
+ for name in cls.testdata.iterkeys():
+ def m(self, name=name):
+ return self.runSingle(*self.testdata[name])
+
+ setattr(cls, 'test_%s' % name, m)
+ return cls
+
+class TestBase(unittest.TestCase):
+ def assertEqual(self, a, b, msg=""):
+ """Actually print the values which weren't equal, if things don't work out!"""
+ unittest.TestCase.assertEqual(self, a, b, "%s got %r expected %r" % (msg, a, b))
+
+class DataTest(TestBase):
+ testdata = {
+ 'oneline':
+ ("He\tllo", "f", 1, 0,
+ ((0, "f", 1, 0), (2, "f", 1, 2), (3, "f", 1, 4))),
+ 'twoline':
+ ("line1 \n\tl\tine2", "f", 1, 4,
+ ((0, "f", 1, 4), (5, "f", 1, 9), (6, "f", 1, 10), (7, "f", 2, 0), (8, "f", 2, 4), (10, "f", 2, 8), (13, "f", 2, 11))),
+ }
+
+ def runSingle(self, data, filename, line, col, results):
+ d = pymake.parser.Data(data, 0, len(data), pymake.parserdata.Location(filename, line, col))
+ for pos, file, lineno, col in results:
+ loc = d.getloc(pos)
+ self.assertEqual(loc.path, file, "data file offset %i" % pos)
+ self.assertEqual(loc.line, lineno, "data line offset %i" % pos)
+ self.assertEqual(loc.column, col, "data col offset %i" % pos)
+multitest(DataTest)
+
+class LineEnumeratorTest(TestBase):
+ testdata = {
+ 'simple': (
+ 'Hello, world', [
+ ('Hello, world', 1),
+ ]
+ ),
+ 'multi': (
+ 'Hello\nhappy \n\nworld\n', [
+ ('Hello', 1),
+ ('happy ', 2),
+ ('', 3),
+ ('world', 4),
+ ('', 5),
+ ]
+ ),
+ 'continuation': (
+ 'Hello, \\\n world\nJellybeans!', [
+ ('Hello, \\\n world', 1),
+ ('Jellybeans!', 3),
+ ]
+ ),
+ 'multislash': (
+ 'Hello, \\\\\n world', [
+ ('Hello, \\\\', 1),
+ (' world', 2),
+ ]
+ )
+ }
+
+ def runSingle(self, s, lines):
+ gotlines = [(d.s[d.lstart:d.lend], d.loc.line) for d in pymake.parser.enumeratelines(s, 'path')]
+ self.assertEqual(gotlines, lines)
+
+multitest(LineEnumeratorTest)
+
+class IterTest(TestBase):
+ testdata = {
+ 'plaindata': (
+ pymake.parser.iterdata,
+ "plaindata # test\n",
+ "plaindata # test\n"
+ ),
+ 'makecomment': (
+ pymake.parser.itermakefilechars,
+ "VAR = val # comment",
+ "VAR = val "
+ ),
+ 'makeescapedcomment': (
+ pymake.parser.itermakefilechars,
+ "VAR = val \# escaped hash",
+ "VAR = val # escaped hash"
+ ),
+ 'makeescapedslash': (
+ pymake.parser.itermakefilechars,
+ "VAR = val\\\\",
+ "VAR = val\\\\",
+ ),
+ 'makecontinuation': (
+ pymake.parser.itermakefilechars,
+ "VAR = VAL \\\n continuation # comment \\\n continuation",
+ "VAR = VAL continuation "
+ ),
+ 'makecontinuation2': (
+ pymake.parser.itermakefilechars,
+ "VAR = VAL \\ \\\n continuation",
+ "VAR = VAL \\ continuation"
+ ),
+ 'makeawful': (
+ pymake.parser.itermakefilechars,
+ "VAR = VAL \\\\# comment\n",
+ "VAR = VAL \\"
+ ),
+ 'command': (
+ pymake.parser.itercommandchars,
+ "echo boo # comment",
+ "echo boo # comment",
+ ),
+ 'commandcomment': (
+ pymake.parser.itercommandchars,
+ "echo boo \# comment",
+ "echo boo \# comment",
+ ),
+ 'commandcontinue': (
+ pymake.parser.itercommandchars,
+ "echo boo # \\\n\t command 2",
+ "echo boo # \\\n command 2"
+ ),
+ }
+
+ def runSingle(self, ifunc, idata, expected):
+ d = pymake.parser.Data.fromstring(idata, 'IterTest data')
+
+ it = pymake.parser._alltokens.finditer(d.s, 0, d.lend)
+ actual = ''.join( [c for c, t, o, oo in ifunc(d, 0, ('dummy-token',), it)] )
+ self.assertEqual(actual, expected)
+
+ if ifunc == pymake.parser.itermakefilechars:
+ print "testing %r" % expected
+ self.assertEqual(pymake.parser.flattenmakesyntax(d, 0), expected)
+
+multitest(IterTest)
+
+
+# 'define': (
+# pymake.parser.iterdefinechars,
+# "endef",
+# ""
+# ),
+# 'definenesting': (
+# pymake.parser.iterdefinechars,
+# """define BAR # comment
+#random text
+#endef not what you think!
+#endef # comment is ok\n""",
+# """define BAR # comment
+#random text
+#endef not what you think!"""
+# ),
+# 'defineescaped': (
+# pymake.parser.iterdefinechars,
+# """value \\
+#endef
+#endef\n""",
+# "value endef"
+# ),
+
+class MakeSyntaxTest(TestBase):
+ # (string, startat, stopat, stopoffset, expansion
+ testdata = {
+ 'text': ('hello world', 0, (), None, ['hello world']),
+ 'singlechar': ('hello $W', 0, (), None,
+ ['hello ',
+ {'type': 'VariableRef',
+ '.vname': ['W']}
+ ]),
+ 'stopat': ('hello: world', 0, (':', '='), 6, ['hello']),
+ 'funccall': ('h $(flavor FOO)', 0, (), None,
+ ['h ',
+ {'type': 'FlavorFunction',
+ '[0]': ['FOO']}
+ ]),
+ 'escapedollar': ('hello$$world', 0, (), None, ['hello$world']),
+ 'varref': ('echo $(VAR)', 0, (), None,
+ ['echo ',
+ {'type': 'VariableRef',
+ '.vname': ['VAR']}
+ ]),
+ 'dynamicvarname': ('echo $($(VARNAME):.c=.o)', 0, (':',), None,
+ ['echo ',
+ {'type': 'SubstitutionRef',
+ '.vname': [{'type': 'VariableRef',
+ '.vname': ['VARNAME']}
+ ],
+ '.substfrom': ['.c'],
+ '.substto': ['.o']}
+ ]),
+ 'substref': (' $(VAR:VAL) := $(VAL)', 0, (':=', '+=', '=', ':'), 15,
+ [' ',
+ {'type': 'VariableRef',
+ '.vname': ['VAR:VAL']},
+ ' ']),
+ 'vadsubstref': (' $(VAR:VAL) = $(VAL)', 15, (), None,
+ [{'type': 'VariableRef',
+ '.vname': ['VAL']},
+ ]),
+ }
+
+ def compareRecursive(self, actual, expected, path):
+ self.assertEqual(len(actual), len(expected),
+ "compareRecursive: %s %r" % (path, actual))
+ for i in xrange(0, len(actual)):
+ ipath = path + [i]
+
+ a, isfunc = actual[i]
+ e = expected[i]
+ if isinstance(e, str):
+ self.assertEqual(a, e, "compareRecursive: %s" % (ipath,))
+ else:
+ self.assertEqual(type(a), getattr(pymake.functions, e['type']),
+ "compareRecursive: %s" % (ipath,))
+ for k, v in e.iteritems():
+ if k == 'type':
+ pass
+ elif k[0] == '[':
+ item = int(k[1:-1])
+ proppath = ipath + [item]
+ self.compareRecursive(a[item], v, proppath)
+ elif k[0] == '.':
+ item = k[1:]
+ proppath = ipath + [item]
+ self.compareRecursive(getattr(a, item), v, proppath)
+ else:
+ raise Exception("Unexpected property at %s: %s" % (ipath, k))
+
+ def runSingle(self, s, startat, stopat, stopoffset, expansion):
+ d = pymake.parser.Data.fromstring(s, pymake.parserdata.Location('testdata', 1, 0))
+
+ a, t, offset = pymake.parser.parsemakesyntax(d, startat, stopat, pymake.parser.itermakefilechars)
+ self.compareRecursive(a, expansion, [])
+ self.assertEqual(offset, stopoffset)
+
+multitest(MakeSyntaxTest)
+
+class VariableTest(TestBase):
+ testdata = """
+ VAR = value
+ VARNAME = TESTVAR
+ $(VARNAME) = testvalue
+ $(VARNAME:VAR=VAL) = moretesting
+ IMM := $(VARNAME) # this is a comment
+ MULTIVAR = val1 \\
+ val2
+ VARNAME = newname
+ """
+ expected = {'VAR': 'value',
+ 'VARNAME': 'newname',
+ 'TESTVAR': 'testvalue',
+ 'TESTVAL': 'moretesting',
+ 'IMM': 'TESTVAR ',
+ 'MULTIVAR': 'val1 val2',
+ 'UNDEF': None}
+
+ def runTest(self):
+ stmts = pymake.parser.parsestring(self.testdata, 'VariableTest')
+
+ m = pymake.data.Makefile()
+ stmts.execute(m)
+ for k, v in self.expected.iteritems():
+ flavor, source, val = m.variables.get(k)
+ if val is None:
+ self.assertEqual(val, v, 'variable named %s' % k)
+ else:
+ self.assertEqual(val.resolvestr(m, m.variables), v, 'variable named %s' % k)
+
+class SimpleRuleTest(TestBase):
+ testdata = """
+ VAR = value
+TSPEC = dummy
+all: TSPEC = myrule
+all:: test test2 $(VAR)
+ echo "Hello, $(TSPEC)"
+
+%.o: %.c
+ $(CC) -o $@ $<
+"""
+
+ def runTest(self):
+ stmts = pymake.parser.parsestring(self.testdata, 'SimpleRuleTest')
+
+ m = pymake.data.Makefile()
+ stmts.execute(m)
+ self.assertEqual(m.defaulttarget, 'all', "Default target")
+
+ self.assertTrue(m.hastarget('all'), "Has 'all' target")
+ target = m.gettarget('all')
+ rules = target.rules
+ self.assertEqual(len(rules), 1, "Number of rules")
+ prereqs = rules[0].prerequisites
+ self.assertEqual(prereqs, ['test', 'test2', 'value'], "Prerequisites")
+ commands = rules[0].commands
+ self.assertEqual(len(commands), 1, "Number of commands")
+ expanded = commands[0].resolvestr(m, target.variables)
+ self.assertEqual(expanded, 'echo "Hello, myrule"')
+
+ irules = m.implicitrules
+ self.assertEqual(len(irules), 1, "Number of implicit rules")
+
+ irule = irules[0]
+ self.assertEqual(len(irule.targetpatterns), 1, "%.o target pattern count")
+ self.assertEqual(len(irule.prerequisites), 1, "%.o prerequisite count")
+ self.assertEqual(irule.targetpatterns[0].match('foo.o'), 'foo', "%.o stem")
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.DEBUG)
+ unittest.main()
diff --git a/build/pymake/tests/path-length.mk b/build/pymake/tests/path-length.mk
new file mode 100644
index 000000000..10c33b5ed
--- /dev/null
+++ b/build/pymake/tests/path-length.mk
@@ -0,0 +1,9 @@
+#T gmake skip
+
+$(shell \
+mkdir foo; \
+touch tfile; \
+)
+
+all: foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../tfile
+ @echo TEST-PASS
diff --git a/build/pymake/tests/pathdir/pathtest b/build/pymake/tests/pathdir/pathtest
new file mode 100755
index 000000000..17037159f
--- /dev/null
+++ b/build/pymake/tests/pathdir/pathtest
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo Called shell script: 2f7cdd0b-7277-48c1-beaf-56cb0dbacb24
diff --git a/build/pymake/tests/pathdir/pathtest.exe b/build/pymake/tests/pathdir/pathtest.exe
new file mode 100644
index 000000000..3178db9a9
--- /dev/null
+++ b/build/pymake/tests/pathdir/pathtest.exe
Binary files differ
diff --git a/build/pymake/tests/pathdir/src/Makefile b/build/pymake/tests/pathdir/src/Makefile
new file mode 100644
index 000000000..6c24bd8f9
--- /dev/null
+++ b/build/pymake/tests/pathdir/src/Makefile
@@ -0,0 +1,2 @@
+pathtest.exe: pathtest.cpp
+ cl -EHsc -MT $^
diff --git a/build/pymake/tests/pathdir/src/pathtest.cpp b/build/pymake/tests/pathdir/src/pathtest.cpp
new file mode 100644
index 000000000..bef8d8a11
--- /dev/null
+++ b/build/pymake/tests/pathdir/src/pathtest.cpp
@@ -0,0 +1,6 @@
+#include <cstdio>
+
+int main() {
+ std::printf("Called Windows executable: 2f7cdd0b-7277-48c1-beaf-56cb0dbacb24\n");
+ return 0;
+}
diff --git a/build/pymake/tests/patsubst.mk b/build/pymake/tests/patsubst.mk
new file mode 100644
index 000000000..0c3efdc4b
--- /dev/null
+++ b/build/pymake/tests/patsubst.mk
@@ -0,0 +1,7 @@
+all:
+ test "$(patsubst foo,%.bar,foo)" = "%.bar"
+ test "$(patsubst \%word,replace,word %word other)" = "word replace other"
+ test "$(patsubst %.c,\%%.o,foo.c bar.o baz.cpp)" = "%foo.o bar.o baz.cpp"
+ test "$(patsubst host_%.c,host_%.o,dir/host_foo.c host_bar.c)" = "dir/host_foo.c host_bar.o"
+ test "$(patsubst foo,bar,dir/foo foo baz)" = "dir/foo bar baz"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/phony.mk b/build/pymake/tests/phony.mk
new file mode 100644
index 000000000..36db4d121
--- /dev/null
+++ b/build/pymake/tests/phony.mk
@@ -0,0 +1,10 @@
+$(shell \
+touch dep; \
+sleep 2; \
+touch all; \
+)
+
+all:: dep
+ @echo TEST-PASS
+
+.PHONY: all
diff --git a/build/pymake/tests/pycmd.py b/build/pymake/tests/pycmd.py
new file mode 100644
index 000000000..83b9b966b
--- /dev/null
+++ b/build/pymake/tests/pycmd.py
@@ -0,0 +1,38 @@
+import os, sys, subprocess
+
+def writetofile(args):
+ with open(args[0], 'w') as f:
+ f.write(' '.join(args[1:]))
+
+def writeenvtofile(args):
+ with open(args[0], 'w') as f:
+ f.write(os.environ[args[1]])
+
+def writesubprocessenvtofile(args):
+ with open(args[0], 'w') as f:
+ p = subprocess.Popen([sys.executable, "-c",
+ "import os; print os.environ['%s']" % args[1]],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ assert p.returncode == 0
+ f.write(stdout)
+
+def convertasplode(arg):
+ try:
+ return int(arg)
+ except:
+ return (None if arg == "None" else arg)
+
+def asplode(args):
+ arg0 = convertasplode(args[0])
+ sys.exit(arg0)
+
+def asplode_return(args):
+ arg0 = convertasplode(args[0])
+ return arg0
+
+def asplode_raise(args):
+ raise Exception(args[0])
+
+def delayloadfn(args):
+ import delayload
diff --git a/build/pymake/tests/recursive-set.mk b/build/pymake/tests/recursive-set.mk
new file mode 100644
index 000000000..853f90463
--- /dev/null
+++ b/build/pymake/tests/recursive-set.mk
@@ -0,0 +1,7 @@
+#T returncode: 2
+
+FOO = $(FOO)
+
+all:
+ echo $(FOO)
+ @echo TEST-FAIL
diff --git a/build/pymake/tests/recursive-set2.mk b/build/pymake/tests/recursive-set2.mk
new file mode 100644
index 000000000..b68e34f0d
--- /dev/null
+++ b/build/pymake/tests/recursive-set2.mk
@@ -0,0 +1,8 @@
+#T returncode: 2
+
+FOO = $(BAR)
+BAR = $(FOO)
+
+all:
+ echo $(FOO)
+ @echo TEST-FAIL
diff --git a/build/pymake/tests/remake-mtime.mk b/build/pymake/tests/remake-mtime.mk
new file mode 100644
index 000000000..47c775b93
--- /dev/null
+++ b/build/pymake/tests/remake-mtime.mk
@@ -0,0 +1,14 @@
+# mtime(dep1) < mtime(target) so the target should not be made
+$(shell touch dep1; sleep 1; touch target)
+
+all: target
+ echo TEST-PASS
+
+target: dep1
+ echo TEST-FAIL target should not have been made
+
+dep1: dep2
+ @echo "Remaking dep1 (actually not)"
+
+dep2:
+ @echo "Making dep2 (actually not)"
diff --git a/build/pymake/tests/rm-fail.mk b/build/pymake/tests/rm-fail.mk
new file mode 100644
index 000000000..1a9aefb57
--- /dev/null
+++ b/build/pymake/tests/rm-fail.mk
@@ -0,0 +1,7 @@
+#T returncode: 2
+all:
+ mkdir newdir
+ test -d newdir
+ touch newdir/newfile
+ $(RM) newdir
+ @echo TEST-PASS
diff --git a/build/pymake/tests/rm.mk b/build/pymake/tests/rm.mk
new file mode 100644
index 000000000..6c7140e39
--- /dev/null
+++ b/build/pymake/tests/rm.mk
@@ -0,0 +1,21 @@
+all:
+# $(RM) defaults to -f
+ $(RM) nosuchfile
+ touch newfile
+ test -f newfile
+ $(RM) newfile
+ test ! -f newfile
+ mkdir newdir
+ test -d newdir
+ touch newdir/newfile
+ mkdir newdir/subdir
+ $(RM) -r newdir/subdir
+ test ! -d newdir/subdir
+ test -d newdir
+ mkdir newdir/subdir1 newdir/subdir2
+ $(RM) -r newdir/subdir1 newdir/subdir2
+ test ! -d newdir/subdir1 -a ! -d newdir/subdir2
+ test -d newdir
+ $(RM) -r newdir
+ test ! -d newdir
+ @echo TEST-PASS
diff --git a/build/pymake/tests/runtests.py b/build/pymake/tests/runtests.py
new file mode 100644
index 000000000..ab149ecfb
--- /dev/null
+++ b/build/pymake/tests/runtests.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python
+"""
+Run the test(s) listed on the command line. If a directory is listed, the script will recursively
+walk the directory for files named .mk and run each.
+
+For each test, we run gmake -f test.mk. By default, make must exit with an exit code of 0, and must print 'TEST-PASS'.
+
+Each test is run in an empty directory.
+
+The test file may contain lines at the beginning to alter the default behavior. These are all evaluated as python:
+
+#T commandline: ['extra', 'params', 'here']
+#T returncode: 2
+#T returncode-on: {'win32': 2}
+#T environment: {'VAR': 'VALUE}
+#T grep-for: "text"
+"""
+
+from subprocess import Popen, PIPE, STDOUT
+from optparse import OptionParser
+import os, re, sys, shutil, glob
+
+class ParentDict(dict):
+ def __init__(self, parent, **kwargs):
+ self.d = dict(kwargs)
+ self.parent = parent
+
+ def __setitem__(self, k, v):
+ self.d[k] = v
+
+ def __getitem__(self, k):
+ if k in self.d:
+ return self.d[k]
+
+ return self.parent[k]
+
+thisdir = os.path.dirname(os.path.abspath(__file__))
+
+pymake = [sys.executable, os.path.join(os.path.dirname(thisdir), 'make.py')]
+manifest = os.path.join(thisdir, 'tests.manifest')
+
+o = OptionParser()
+o.add_option('-g', '--gmake',
+ dest="gmake", default="gmake")
+o.add_option('-d', '--tempdir',
+ dest="tempdir", default="_mktests")
+opts, args = o.parse_args()
+
+if len(args) == 0:
+ args = [thisdir]
+
+makefiles = []
+for a in args:
+ if os.path.isfile(a):
+ makefiles.append(a)
+ elif os.path.isdir(a):
+ makefiles.extend(sorted(glob.glob(os.path.join(a, '*.mk'))))
+
+def runTest(makefile, make, logfile, options):
+ """
+ Given a makefile path, test it with a given `make` and return
+ (pass, message).
+ """
+
+ if os.path.exists(opts.tempdir): shutil.rmtree(opts.tempdir)
+ os.mkdir(opts.tempdir, 0755)
+
+ logfd = open(logfile, 'w')
+ p = Popen(make + options['commandline'], stdout=logfd, stderr=STDOUT, env=options['env'])
+ logfd.close()
+ retcode = p.wait()
+
+ if retcode != options['returncode']:
+ return False, "FAIL (returncode=%i)" % retcode
+
+ logfd = open(logfile)
+ stdout = logfd.read()
+ logfd.close()
+
+ if stdout.find('TEST-FAIL') != -1:
+ print stdout
+ return False, "FAIL (TEST-FAIL printed)"
+
+ if options['grepfor'] and stdout.find(options['grepfor']) == -1:
+ print stdout
+ return False, "FAIL (%s not in output)" % options['grepfor']
+
+ if options['returncode'] == 0 and stdout.find('TEST-PASS') == -1:
+ print stdout
+ return False, 'FAIL (No TEST-PASS printed)'
+
+ if options['returncode'] != 0:
+ return True, 'PASS (retcode=%s)' % retcode
+
+ return True, 'PASS'
+
+print "%-30s%-28s%-28s" % ("Test:", "gmake:", "pymake:")
+
+gmakefails = 0
+pymakefails = 0
+
+tre = re.compile('^#T (gmake |pymake )?([a-z-]+)(?:: (.*))?$')
+
+for makefile in makefiles:
+ # For some reason, MAKEFILE_LIST uses native paths in GNU make on Windows
+ # (even in MSYS!) so we pass both TESTPATH and NATIVE_TESTPATH
+ cline = ['-C', opts.tempdir, '-f', os.path.abspath(makefile), 'TESTPATH=%s' % thisdir.replace('\\','/'), 'NATIVE_TESTPATH=%s' % thisdir]
+ if sys.platform == 'win32':
+ #XXX: hack so we can specialize the separator character on windows.
+ # we really shouldn't need this, but y'know
+ cline += ['__WIN32__=1']
+
+ options = {
+ 'returncode': 0,
+ 'grepfor': None,
+ 'env': dict(os.environ),
+ 'commandline': cline,
+ 'pass': True,
+ 'skip': False,
+ }
+
+ gmakeoptions = ParentDict(options)
+ pymakeoptions = ParentDict(options)
+
+ dmap = {None: options, 'gmake ': gmakeoptions, 'pymake ': pymakeoptions}
+
+ mdata = open(makefile)
+ for line in mdata:
+ line = line.strip()
+ m = tre.search(line)
+ if m is None:
+ break
+
+ make, key, data = m.group(1, 2, 3)
+ d = dmap[make]
+ if data is not None:
+ data = eval(data)
+ if key == 'commandline':
+ assert make is None
+ d['commandline'].extend(data)
+ elif key == 'returncode':
+ d['returncode'] = data
+ elif key == 'returncode-on':
+ if sys.platform in data:
+ d['returncode'] = data[sys.platform]
+ elif key == 'environment':
+ for k, v in data.iteritems():
+ d['env'][k] = v
+ elif key == 'grep-for':
+ d['grepfor'] = data
+ elif key == 'fail':
+ d['pass'] = False
+ elif key == 'skip':
+ d['skip'] = True
+ else:
+ print >>sys.stderr, "%s: Unexpected #T key: %s" % (makefile, key)
+ sys.exit(1)
+
+ mdata.close()
+
+ if gmakeoptions['skip']:
+ gmakepass, gmakemsg = True, ''
+ else:
+ gmakepass, gmakemsg = runTest(makefile, [opts.gmake],
+ makefile + '.gmakelog', gmakeoptions)
+
+ if gmakeoptions['pass']:
+ if not gmakepass:
+ gmakefails += 1
+ else:
+ if gmakepass:
+ gmakefails += 1
+ gmakemsg = "UNEXPECTED PASS"
+ else:
+ gmakemsg = "KNOWN FAIL"
+
+ if pymakeoptions['skip']:
+ pymakepass, pymakemsg = True, ''
+ else:
+ pymakepass, pymakemsg = runTest(makefile, pymake,
+ makefile + '.pymakelog', pymakeoptions)
+
+ if pymakeoptions['pass']:
+ if not pymakepass:
+ pymakefails += 1
+ else:
+ if pymakepass:
+ pymakefails += 1
+ pymakemsg = "UNEXPECTED PASS"
+ else:
+ pymakemsg = "OK (known fail)"
+
+ print "%-30.30s%-28.28s%-28.28s" % (os.path.basename(makefile),
+ gmakemsg, pymakemsg)
+
+print
+print "Summary:"
+print "%-30s%-28s%-28s" % ("", "gmake:", "pymake:")
+
+if gmakefails == 0:
+ gmakemsg = 'PASS'
+else:
+ gmakemsg = 'FAIL (%i failures)' % gmakefails
+
+if pymakefails == 0:
+ pymakemsg = 'PASS'
+else:
+ pymakemsg = 'FAIL (%i failures)' % pymakefails
+
+print "%-30.30s%-28.28s%-28.28s" % ('', gmakemsg, pymakemsg)
+
+shutil.rmtree(opts.tempdir)
+
+if gmakefails or pymakefails:
+ sys.exit(1)
diff --git a/build/pymake/tests/serial-dep-resolution.mk b/build/pymake/tests/serial-dep-resolution.mk
new file mode 100644
index 000000000..e65f1ed03
--- /dev/null
+++ b/build/pymake/tests/serial-dep-resolution.mk
@@ -0,0 +1,5 @@
+all: t1 t2
+ @echo TEST-PASS
+
+t1:
+ touch t1 t2
diff --git a/build/pymake/tests/serial-doublecolon-execution.mk b/build/pymake/tests/serial-doublecolon-execution.mk
new file mode 100644
index 000000000..1871cb13a
--- /dev/null
+++ b/build/pymake/tests/serial-doublecolon-execution.mk
@@ -0,0 +1,18 @@
+#T commandline: ['-j3']
+
+# Commands of double-colon rules are always executed in order.
+
+all: dc
+ cat status
+ test "$$(cat status)" = "all1:all2:"
+ @echo TEST-PASS
+
+dc:: slowt
+ printf "all1:" >> status
+
+dc::
+ sleep 0.2
+ printf "all2:" >> status
+
+slowt:
+ sleep 1
diff --git a/build/pymake/tests/serial-rule-execution.mk b/build/pymake/tests/serial-rule-execution.mk
new file mode 100644
index 000000000..da5b177de
--- /dev/null
+++ b/build/pymake/tests/serial-rule-execution.mk
@@ -0,0 +1,5 @@
+all::
+ touch somefile
+
+all:: somefile
+ @echo TEST-PASS
diff --git a/build/pymake/tests/serial-rule-execution2.mk b/build/pymake/tests/serial-rule-execution2.mk
new file mode 100644
index 000000000..252a7df83
--- /dev/null
+++ b/build/pymake/tests/serial-rule-execution2.mk
@@ -0,0 +1,13 @@
+#T returncode: 2
+
+# The dependencies of the command rule of a single-colon target are resolved before the rules without commands.
+
+all: export
+
+export:
+ sleep 1
+ touch somefile
+
+all: somefile
+ test -f somefile
+ @echo TEST-PASS
diff --git a/build/pymake/tests/serial-toparallel.mk b/build/pymake/tests/serial-toparallel.mk
new file mode 100644
index 000000000..a980badc7
--- /dev/null
+++ b/build/pymake/tests/serial-toparallel.mk
@@ -0,0 +1,5 @@
+all::
+ $(MAKE) -j2 -f $(TESTPATH)/parallel-simple.mk
+
+all:: results
+ @echo TEST-PASS
diff --git a/build/pymake/tests/shellfunc.mk b/build/pymake/tests/shellfunc.mk
new file mode 100644
index 000000000..1e408dbac
--- /dev/null
+++ b/build/pymake/tests/shellfunc.mk
@@ -0,0 +1,7 @@
+all: testfile
+ test "$(shell cat $<)" = "Hello world"
+ test "$(shell printf "\n")" = ""
+ @echo TEST-PASS
+
+testfile:
+ printf "Hello\nworld\n" > $@
diff --git a/build/pymake/tests/simple-makeflags.mk b/build/pymake/tests/simple-makeflags.mk
new file mode 100644
index 000000000..c7c92ec9d
--- /dev/null
+++ b/build/pymake/tests/simple-makeflags.mk
@@ -0,0 +1,10 @@
+# There once was a time when MAKEFLAGS=w without any following spaces would
+# cause us to treat w as a target, not a flag. Silly!
+
+MAKEFLAGS=w
+
+all:
+ $(MAKE) -f $(TESTPATH)/simple-makeflags.mk subt
+ @echo TEST-PASS
+
+subt:
diff --git a/build/pymake/tests/sort.mk b/build/pymake/tests/sort.mk
new file mode 100644
index 000000000..e1313ad5c
--- /dev/null
+++ b/build/pymake/tests/sort.mk
@@ -0,0 +1,4 @@
+# sort should remove duplicates
+all:
+ @test "$(sort x a y b z c a z b x c y)" = "a b c x y z"
+ @echo "TEST-PASS"
diff --git a/build/pymake/tests/specified-target.mk b/build/pymake/tests/specified-target.mk
new file mode 100644
index 000000000..3b23fbf69
--- /dev/null
+++ b/build/pymake/tests/specified-target.mk
@@ -0,0 +1,7 @@
+#T commandline: ['VAR=all', '$(VAR)']
+
+all:
+ @echo TEST-FAIL: unexpected target 'all'
+
+$$(VAR):
+ @echo TEST-PASS: expected target '$$(VAR)'
diff --git a/build/pymake/tests/static-pattern.mk b/build/pymake/tests/static-pattern.mk
new file mode 100644
index 000000000..f613b8c9a
--- /dev/null
+++ b/build/pymake/tests/static-pattern.mk
@@ -0,0 +1,5 @@
+#T returncode: 2
+
+out/host_foo.o: host_%.o: host_%.c out
+ cp $< $@
+ @echo TEST-FAIL
diff --git a/build/pymake/tests/static-pattern2.mk b/build/pymake/tests/static-pattern2.mk
new file mode 100644
index 000000000..08ed834fd
--- /dev/null
+++ b/build/pymake/tests/static-pattern2.mk
@@ -0,0 +1,10 @@
+all: foo.out
+ test -f $^
+ @echo TEST-PASS
+
+foo.out: %.out: %.in
+ test "$*" = "foo"
+ cp $^ $@
+
+foo.in:
+ touch $@
diff --git a/build/pymake/tests/subdir/delayload.py b/build/pymake/tests/subdir/delayload.py
new file mode 100644
index 000000000..bdd6669db
--- /dev/null
+++ b/build/pymake/tests/subdir/delayload.py
@@ -0,0 +1 @@
+# This module exists to test delay importing of modules at run-time.
diff --git a/build/pymake/tests/subdir/pymod.py b/build/pymake/tests/subdir/pymod.py
new file mode 100644
index 000000000..1a47d8af2
--- /dev/null
+++ b/build/pymake/tests/subdir/pymod.py
@@ -0,0 +1,5 @@
+import testmodule
+
+def writetofile(args):
+ with open(args[0], 'w') as f:
+ f.write(' '.join(args[1:]))
diff --git a/build/pymake/tests/subdir/testmodule.py b/build/pymake/tests/subdir/testmodule.py
new file mode 100644
index 000000000..05b2f821a
--- /dev/null
+++ b/build/pymake/tests/subdir/testmodule.py
@@ -0,0 +1,3 @@
+# This is an empty module. It is imported by pymod.py to test that if a module
+# is loaded from the PYCOMMANDPATH, it can import other modules from the same
+# directory correctly.
diff --git a/build/pymake/tests/submake-path.makefile2 b/build/pymake/tests/submake-path.makefile2
new file mode 100644
index 000000000..1266db7d1
--- /dev/null
+++ b/build/pymake/tests/submake-path.makefile2
@@ -0,0 +1,11 @@
+# -*- Mode: Makefile -*-
+
+shellresult := $(shell pathtest)
+ifneq (2f7cdd0b-7277-48c1-beaf-56cb0dbacb24,$(filter $(shellresult),2f7cdd0b-7277-48c1-beaf-56cb0dbacb24))
+$(error pathtest not found in submake shell function)
+endif
+
+all:
+ @pathtest
+ @pathtest | grep -q 2f7cdd0b-7277-48c1-beaf-56cb0dbacb24
+ @echo TEST-PASS
diff --git a/build/pymake/tests/submake-path.mk b/build/pymake/tests/submake-path.mk
new file mode 100644
index 000000000..b6432276d
--- /dev/null
+++ b/build/pymake/tests/submake-path.mk
@@ -0,0 +1,16 @@
+#T gmake skip
+#T grep-for: "2f7cdd0b-7277-48c1-beaf-56cb0dbacb24"
+
+ifdef __WIN32__
+PS:=;
+else
+PS:=:
+endif
+
+export PATH := $(TESTPATH)/pathdir$(PS)$(PATH)
+
+# This is similar to subprocess-path.mk, except we also check $(shell)
+# invocations since they're affected by exported environment variables too,
+# but only in submakes!
+all:
+ $(MAKE) -f $(TESTPATH)/submake-path.makefile2
diff --git a/build/pymake/tests/submake.makefile2 b/build/pymake/tests/submake.makefile2
new file mode 100644
index 000000000..12ce94834
--- /dev/null
+++ b/build/pymake/tests/submake.makefile2
@@ -0,0 +1,24 @@
+# -*- Mode: Makefile -*-
+
+$(info MAKEFLAGS = '$(MAKEFLAGS)')
+$(info MAKE = '$(MAKE)')
+$(info value MAKE = "$(value MAKE)")
+
+shellresult := $(shell echo -n $$EVAR)
+ifneq ($(shellresult),eval)
+$(error EVAR should be eval, is instead $(shellresult))
+endif
+
+all:
+ env
+ test "$(MAKELEVEL)" = "1"
+ echo "value(MAKE)" '$(value MAKE)'
+ echo "value(MAKE_COMMAND)" = '$(value MAKE_COMMAND)'
+ test "$(origin CVAR)" = "command line"
+ test "$(CVAR)" = "c val=spac\ed"
+ test "$(origin EVAR)" = "environment"
+ test "$(EVAR)" = "eval"
+ test "$(OVAL)" = "cline"
+ test "$(OVAL2)" = "cline2"
+ test "$(ALLVAR)" = "allspecific"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/submake.mk b/build/pymake/tests/submake.mk
new file mode 100644
index 000000000..41e47134b
--- /dev/null
+++ b/build/pymake/tests/submake.mk
@@ -0,0 +1,16 @@
+#T commandline: ['CVAR=c val=spac\\ed', 'OVAL=cline', 'OVAL2=cline2']
+
+export EVAR = eval
+override OVAL = makefile
+
+# exporting an override variable doesn't mean it's an override variable
+override OVAL2 = makefile2
+export OVAL2
+
+export ALLVAR
+ALLVAR = general
+all: ALLVAR = allspecific
+
+all:
+ test "$(MAKELEVEL)" = "0"
+ $(MAKE) -f $(TESTPATH)/submake.makefile2
diff --git a/build/pymake/tests/subprocess-path.mk b/build/pymake/tests/subprocess-path.mk
new file mode 100644
index 000000000..f63921414
--- /dev/null
+++ b/build/pymake/tests/subprocess-path.mk
@@ -0,0 +1,32 @@
+#T gmake skip
+#T grep-for: "2f7cdd0b-7277-48c1-beaf-56cb0dbacb24"
+
+ifdef __WIN32__
+PS:=;
+else
+PS:=:
+endif
+
+export PATH := $(TESTPATH)/pathdir$(PS)$(PATH)
+
+# Test two commands. The first one shouldn't go through the shell and the
+# second one should. The pathdir subdirectory has a Windows executable called
+# pathtest.exe and a shell script called pathtest. We don't care which one is
+# run, just that one of the two is (we use a uuid + grep-for to make sure
+# that happens).
+#
+# FAQ:
+# Q. Why skip GNU Make?
+# A. Because $(TESTPATH) is a Windows-style path, and MSYS make doesn't take
+# too kindly to Windows paths in the PATH environment variable.
+#
+# Q. Why use an exe and not a batch file?
+# A. The use cases here were all exe files without the extension. Batch file
+# lookup has broken semantics if the .bat extension isn't passed.
+#
+# Q. Why are the commands silent?
+# A. So that we don't pass the grep-for test by mistake.
+all:
+ @pathtest
+ @pathtest | grep -q 2f7cdd0b-7277-48c1-beaf-56cb0dbacb24
+ @echo TEST-PASS
diff --git a/build/pymake/tests/tab-intro.mk b/build/pymake/tests/tab-intro.mk
new file mode 100644
index 000000000..1c25ce747
--- /dev/null
+++ b/build/pymake/tests/tab-intro.mk
@@ -0,0 +1,16 @@
+# Initial tab characters should be treated well.
+
+ THIS = a value
+
+ ifdef THIS
+ VAR = conditional value
+ endif
+
+all:
+ test "$(THIS)" = "another value"
+ test "$(VAR)" = "conditional value"
+ @echo TEST-PASS
+
+THAT = makefile syntax
+
+ THIS = another value
diff --git a/build/pymake/tests/target-specific.mk b/build/pymake/tests/target-specific.mk
new file mode 100644
index 000000000..217ed155e
--- /dev/null
+++ b/build/pymake/tests/target-specific.mk
@@ -0,0 +1,30 @@
+TESTVAR = anonval
+
+all: target.suffix target.suffix2 dummy host_test.py my.test1 my.test2
+ @echo TEST-PASS
+
+target.suffix: TESTVAR = testval
+
+%.suffix:
+ test "$(TESTVAR)" = "testval"
+
+%.suffix2: TESTVAR = testval2
+
+%.suffix2:
+ test "$(TESTVAR)" = "testval2"
+
+%my: TESTVAR = dummyval
+
+dummy:
+ test "$(TESTVAR)" = "dummyval"
+
+%.py: TESTVAR = pyval
+host_%.py: TESTVAR = hostval
+
+host_test.py:
+ test "$(TESTVAR)" = "hostval"
+
+%.test1 %.test2: TESTVAR = %val
+
+my.test1 my.test2:
+ test "$(TESTVAR)" = "%val"
diff --git a/build/pymake/tests/unexport.mk b/build/pymake/tests/unexport.mk
new file mode 100644
index 000000000..424411603
--- /dev/null
+++ b/build/pymake/tests/unexport.mk
@@ -0,0 +1,15 @@
+#T environment: {'ENVVAR': 'envval'}
+
+VAR1 = val1
+VAR2 = val2
+VAR3 = val3
+
+unexport VAR3
+export VAR1 VAR2 VAR3
+unexport VAR2 ENVVAR
+unexport
+
+all:
+ test "$(ENVVAR)" = "envval" # unexport.mk
+ $(MAKE) -f $(TESTPATH)/unexport.submk
+ @echo TEST-PASS
diff --git a/build/pymake/tests/unexport.submk b/build/pymake/tests/unexport.submk
new file mode 100644
index 000000000..8db6163de
--- /dev/null
+++ b/build/pymake/tests/unexport.submk
@@ -0,0 +1,15 @@
+# -@- Mode: Makefile -@-
+
+unexport VAR1
+
+all:
+ env
+ test "$(VAR1)" = "val1"
+ test "$(origin VAR1)" = "environment"
+ test "$(VAR2)" = "" # VAR2
+ test "$(VAR3)" = "val3"
+ test "$(ENVVAR)" = ""
+ $(MAKE) -f $(TESTPATH)/unexport.submk subt
+
+subt:
+ test "$(VAR1)" = ""
diff --git a/build/pymake/tests/unterminated-dollar.mk b/build/pymake/tests/unterminated-dollar.mk
new file mode 100644
index 000000000..dee9a207b
--- /dev/null
+++ b/build/pymake/tests/unterminated-dollar.mk
@@ -0,0 +1,6 @@
+VAR = value$
+VAR2 = other
+
+all:
+ test "$(VAR)" = "value"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/var-change-flavor.mk b/build/pymake/tests/var-change-flavor.mk
new file mode 100644
index 000000000..0cccf0bd6
--- /dev/null
+++ b/build/pymake/tests/var-change-flavor.mk
@@ -0,0 +1,12 @@
+VAR = value1
+VAR := value2
+
+VAR2 := val1
+VAR2 = val2
+
+default:
+ test "$(flavor VAR)" = "simple"
+ test "$(VAR)" = "value2"
+ test "$(flavor VAR2)" = "recursive"
+ test "$(VAR2)" = "val2"
+ @echo "TEST-PASS"
diff --git a/build/pymake/tests/var-commandline.mk b/build/pymake/tests/var-commandline.mk
new file mode 100644
index 000000000..e2cdad457
--- /dev/null
+++ b/build/pymake/tests/var-commandline.mk
@@ -0,0 +1,8 @@
+#T commandline: ['TESTVAR=$(MAKEVAL)', 'TESTVAR2:=$(MAKEVAL)']
+
+MAKEVAL=testvalue
+
+all:
+ test "$(TESTVAR)" = "testvalue"
+ test "$(TESTVAR2)" = ""
+ @echo "TEST-PASS" \ No newline at end of file
diff --git a/build/pymake/tests/var-overrides.mk b/build/pymake/tests/var-overrides.mk
new file mode 100644
index 000000000..bd0765d19
--- /dev/null
+++ b/build/pymake/tests/var-overrides.mk
@@ -0,0 +1,21 @@
+#T commandline: ['CLINEVAR=clineval', 'CLINEVAR2=clineval2']
+
+# this doesn't actually test overrides yet, because they aren't implemented in pymake,
+# but testing origins in general is important
+
+MVAR = mval
+CLINEVAR = deadbeef
+
+override CLINEVAR2 = mval2
+
+all:
+ test "$(origin NOVAR)" = "undefined"
+ test "$(CLINEVAR)" = "clineval"
+ test "$(origin CLINEVAR)" = "command line"
+ test "$(MVAR)" = "mval"
+ test "$(origin MVAR)" = "file"
+ test "$(@)" = "all"
+ test "$(origin @)" = "automatic"
+ test "$(origin CLINEVAR2)" = "override"
+ test "$(CLINEVAR2)" = "mval2"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/var-ref.mk b/build/pymake/tests/var-ref.mk
new file mode 100644
index 000000000..3bc1886f9
--- /dev/null
+++ b/build/pymake/tests/var-ref.mk
@@ -0,0 +1,19 @@
+VAR = value
+VAR2 == value
+
+VAR5 = $(NULL) $(NULL)
+VARC = value # comment
+
+$(VAR3)
+ $(VAR4)
+$(VAR5)
+
+VAR6$(VAR5) = val6
+
+all:
+ test "$( VAR)" = ""
+ test "$(VAR2)" = "= value"
+ test "${VAR2}" = "= value"
+ test "$(VAR6 )" = "val6"
+ test "$(VARC)" = "value "
+ @echo TEST-PASS
diff --git a/build/pymake/tests/var-set.mk b/build/pymake/tests/var-set.mk
new file mode 100644
index 000000000..1603e7a35
--- /dev/null
+++ b/build/pymake/tests/var-set.mk
@@ -0,0 +1,55 @@
+#T commandline: ['OBASIC=oval']
+
+BASIC = val
+
+TEST = $(TEST)
+
+TEST2 = $(TES
+TEST2 += T)
+
+TES T = val
+
+RECVAR = foo
+RECVAR += var baz
+
+IMMVAR := bloo
+IMMVAR += $(RECVAR)
+
+BASIC ?= notval
+
+all: BASIC = valall
+all: RECVAR += $(BASIC)
+all: IMMVAR += $(BASIC)
+all: UNSET += more
+all: OBASIC += allmore
+
+CHECKLIT = $(NULL) check
+all: CHECKLIT += appendliteral
+
+RECVAR = blimey
+
+TESTEMPTY = \
+ $(NULL)
+
+all: other
+ test "$(TEST2)" = "val"
+ test '$(value TEST2)' = '$$(TES T)'
+ test "$(RECVAR)" = "blimey valall"
+ test "$(IMMVAR)" = "bloo foo var baz valall"
+ test "$(UNSET)" = "more"
+ test "$(OBASIC)" = "oval"
+ test "$(CHECKLIT)" = " check appendliteral"
+ test "$(TESTEMPTY)" = ""
+ @echo TEST-PASS
+
+OVAR = oval
+OVAR ?= onotval
+
+other: OVAR ?= ooval
+other: LATERVAR ?= lateroverride
+
+LATERVAR = olater
+
+other:
+ test "$(OVAR)" = "oval"
+ test "$(LATERVAR)" = "lateroverride"
diff --git a/build/pymake/tests/var-substitutions.mk b/build/pymake/tests/var-substitutions.mk
new file mode 100644
index 000000000..d5627d7bd
--- /dev/null
+++ b/build/pymake/tests/var-substitutions.mk
@@ -0,0 +1,49 @@
+SIMPLEVAR = aabb.cc
+SIMPLEPERCENT = test_value%extra
+
+SIMPLE3SUBSTNAME = SIMPLEVAR:.dd
+$(SIMPLE3SUBSTNAME) = weirdval
+
+PERCENT = dummy
+
+SIMPLESUBST = $(SIMPLEVAR:.cc=.dd)
+SIMPLE2SUBST = $(SIMPLEVAR:.cc)
+SIMPLE3SUBST = $(SIMPLEVAR:.dd)
+SIMPLE4SUBST = $(SIMPLEVAR:.cc=.dd=.ee)
+SIMPLE5SUBST = $(SIMPLEVAR:.cc=%.dd)
+PERCENTSUBST = $(SIMPLEVAR:%.cc=%.ee)
+PERCENT2SUBST = $(SIMPLEVAR:aa%.cc=ff%.f)
+PERCENT3SUBST = $(SIMPLEVAR:aa%.dd=gg%.gg)
+PERCENT4SUBST = $(SIMPLEVAR:aa%.cc=gg)
+PERCENT5SUBST = $(SIMPLEVAR:aa)
+PERCENT6SUBST = $(SIMPLEVAR:%.cc=%.dd=%.ee)
+PERCENT7SUBST = $(SIMPLEVAR:$(PERCENT).cc=%.dd)
+PERCENT8SUBST = $(SIMPLEVAR:%.cc=$(PERCENT).dd)
+PERCENT9SUBST = $(SIMPLEVAR:$(PERCENT).cc=$(PERCENT).dd)
+PERCENT10SUBST = $(SIMPLEVAR:%%.bb.cc=zz.bb.cc)
+PERCENT11SUBST = $(SIMPLEPERCENT:test%value%extra=other%value%extra)
+
+SPACEDVAR = $(NULL) ex1.c ex2.c $(NULL)
+SPACEDSUBST = $(SPACEDVAR:.c=.o)
+
+all:
+ test "$(SIMPLESUBST)" = "aabb.dd"
+ test "$(SIMPLE2SUBST)" = ""
+ test "$(SIMPLE3SUBST)" = "weirdval"
+ test "$(SIMPLE4SUBST)" = "aabb.dd=.ee"
+ test "$(SIMPLE5SUBST)" = "aabb%.dd"
+ test "$(PERCENTSUBST)" = "aabb.ee"
+ test "$(PERCENT2SUBST)" = "ffbb.f"
+ test "$(PERCENT3SUBST)" = "aabb.cc"
+ test "$(PERCENT4SUBST)" = "gg"
+ test "$(PERCENT5SUBST)" = ""
+ test "$(PERCENT6SUBST)" = "aabb.dd=%.ee"
+ test "$(PERCENT7SUBST)" = "aabb.dd"
+ test "$(PERCENT8SUBST)" = "aabb.dd"
+ test "$(PERCENT9SUBST)" = "aabb.dd"
+ test "$(PERCENT10SUBST)" = "aabb.cc"
+ test "$(PERCENT11SUBST)" = "other_value%extra"
+ test "$(SPACEDSUBST)" = "ex1.o ex2.o"
+ @echo TEST-PASS
+
+PERCENT = %
diff --git a/build/pymake/tests/vpath-directive-dynamic.mk b/build/pymake/tests/vpath-directive-dynamic.mk
new file mode 100644
index 000000000..9aa1bf956
--- /dev/null
+++ b/build/pymake/tests/vpath-directive-dynamic.mk
@@ -0,0 +1,12 @@
+$(shell \
+mkdir subd1; \
+touch subd1/test.in; \
+)
+
+VVAR = %.in subd1
+
+vpath $(VVAR)
+
+all: test.in
+ test "$<" = "subd1/test.in"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/vpath-directive.mk b/build/pymake/tests/vpath-directive.mk
new file mode 100644
index 000000000..4c7d4bf39
--- /dev/null
+++ b/build/pymake/tests/vpath-directive.mk
@@ -0,0 +1,31 @@
+# On Windows, MSYS make takes Unix paths but Pymake takes Windows paths
+VPSEP := $(if $(and $(__WIN32__),$(.PYMAKE)),;,:)
+
+$(shell \
+mkdir subd1 subd2 subd3; \
+printf "reallybaddata" >subd1/foo.in; \
+printf "gooddata" >subd2/foo.in; \
+printf "baddata" >subd3/foo.in; \
+touch subd1/foo.in2 subd2/foo.in2 subd3/foo.in2; \
+)
+
+vpath %.in subd
+
+vpath
+vpath %.in subd2$(VPSEP)subd3
+
+vpath %.in2 subd0
+vpath f%.in2 subd1
+vpath %.in2 $(VPSEP)subd2
+
+%.out: %.in
+ test "$<" = "subd2/foo.in"
+ cp $< $@
+
+%.out2: %.in2
+ test "$<" = "subd1/foo.in2"
+ cp $< $@
+
+all: foo.out foo.out2
+ test "$$(cat foo.out)" = "gooddata"
+ @echo TEST-PASS
diff --git a/build/pymake/tests/vpath.mk b/build/pymake/tests/vpath.mk
new file mode 100644
index 000000000..06f52180c
--- /dev/null
+++ b/build/pymake/tests/vpath.mk
@@ -0,0 +1,18 @@
+VPATH = foo bar
+
+$(shell \
+mkdir foo; touch foo/tfile1; \
+mkdir bar; touch bar/tfile2 bar/tfile3 bar/test.objtest; \
+sleep 2; \
+touch bar/test.source; \
+)
+
+all: tfile1 tfile2 tfile3 test.objtest test.source
+ test "$^" = "foo/tfile1 bar/tfile2 tfile3 test.objtest bar/test.source"
+ @echo TEST-PASS
+
+tfile3: test.objtest
+
+%.objtest: %.source
+ test "$<" = bar/test.source
+ test "$@" = test.objtest
diff --git a/build/pymake/tests/vpath2.mk b/build/pymake/tests/vpath2.mk
new file mode 100644
index 000000000..be73ffe5c
--- /dev/null
+++ b/build/pymake/tests/vpath2.mk
@@ -0,0 +1,18 @@
+VPATH = foo bar
+
+$(shell \
+mkdir bar; touch bar/test.source; \
+sleep 2; \
+mkdir foo; touch foo/tfile1; \
+touch bar/tfile2 bar/tfile3 bar/test.objtest; \
+)
+
+all: tfile1 tfile2 tfile3 test.objtest test.source
+ test "$^" = "foo/tfile1 bar/tfile2 bar/tfile3 bar/test.objtest bar/test.source"
+ @echo TEST-PASS
+
+tfile3: test.objtest
+
+%.objtest: %.source
+ test "$<" = bar/test.source
+ test "$@" = test.objtest
diff --git a/build/pymake/tests/wildcards.mk b/build/pymake/tests/wildcards.mk
new file mode 100644
index 000000000..24ff3f14c
--- /dev/null
+++ b/build/pymake/tests/wildcards.mk
@@ -0,0 +1,22 @@
+$(shell \
+mkdir foo; \
+touch a.c b.c c.out foo/d.c; \
+sleep 2; \
+touch c.in; \
+)
+
+VPATH = foo
+
+all: c.out prog
+ cat $<
+ test "$$(cat $<)" = "remadec.out"
+ @echo TEST-PASS
+
+*.out: %.out: %.in
+ test "$@" = c.out
+ test "$<" = c.in
+ printf "remade$@" >$@
+
+prog: *.c
+ test "$^" = "a.c b.c"
+ touch $@
diff --git a/build/pymake/tests/windows-paths.mk b/build/pymake/tests/windows-paths.mk
new file mode 100644
index 000000000..5f33a9050
--- /dev/null
+++ b/build/pymake/tests/windows-paths.mk
@@ -0,0 +1,5 @@
+all:
+ touch file.in
+ printf "%s: %s\n\ttrue" '$(CURDIR)/file.out' '$(CURDIR)/file.in' >test.mk
+ $(MAKE) -f test.mk $(CURDIR)/file.out
+ @echo TEST-PASS