Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | import pymake.data, pymake.parser, pymake.parserdata, pymake.functions |
michael@0 | 2 | import unittest |
michael@0 | 3 | import logging |
michael@0 | 4 | |
michael@0 | 5 | from cStringIO import StringIO |
michael@0 | 6 | |
michael@0 | 7 | def multitest(cls): |
michael@0 | 8 | for name in cls.testdata.iterkeys(): |
michael@0 | 9 | def m(self, name=name): |
michael@0 | 10 | return self.runSingle(*self.testdata[name]) |
michael@0 | 11 | |
michael@0 | 12 | setattr(cls, 'test_%s' % name, m) |
michael@0 | 13 | return cls |
michael@0 | 14 | |
michael@0 | 15 | class TestBase(unittest.TestCase): |
michael@0 | 16 | def assertEqual(self, a, b, msg=""): |
michael@0 | 17 | """Actually print the values which weren't equal, if things don't work out!""" |
michael@0 | 18 | unittest.TestCase.assertEqual(self, a, b, "%s got %r expected %r" % (msg, a, b)) |
michael@0 | 19 | |
michael@0 | 20 | class DataTest(TestBase): |
michael@0 | 21 | testdata = { |
michael@0 | 22 | 'oneline': |
michael@0 | 23 | ("He\tllo", "f", 1, 0, |
michael@0 | 24 | ((0, "f", 1, 0), (2, "f", 1, 2), (3, "f", 1, 4))), |
michael@0 | 25 | 'twoline': |
michael@0 | 26 | ("line1 \n\tl\tine2", "f", 1, 4, |
michael@0 | 27 | ((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))), |
michael@0 | 28 | } |
michael@0 | 29 | |
michael@0 | 30 | def runSingle(self, data, filename, line, col, results): |
michael@0 | 31 | d = pymake.parser.Data(data, 0, len(data), pymake.parserdata.Location(filename, line, col)) |
michael@0 | 32 | for pos, file, lineno, col in results: |
michael@0 | 33 | loc = d.getloc(pos) |
michael@0 | 34 | self.assertEqual(loc.path, file, "data file offset %i" % pos) |
michael@0 | 35 | self.assertEqual(loc.line, lineno, "data line offset %i" % pos) |
michael@0 | 36 | self.assertEqual(loc.column, col, "data col offset %i" % pos) |
michael@0 | 37 | multitest(DataTest) |
michael@0 | 38 | |
michael@0 | 39 | class LineEnumeratorTest(TestBase): |
michael@0 | 40 | testdata = { |
michael@0 | 41 | 'simple': ( |
michael@0 | 42 | 'Hello, world', [ |
michael@0 | 43 | ('Hello, world', 1), |
michael@0 | 44 | ] |
michael@0 | 45 | ), |
michael@0 | 46 | 'multi': ( |
michael@0 | 47 | 'Hello\nhappy \n\nworld\n', [ |
michael@0 | 48 | ('Hello', 1), |
michael@0 | 49 | ('happy ', 2), |
michael@0 | 50 | ('', 3), |
michael@0 | 51 | ('world', 4), |
michael@0 | 52 | ('', 5), |
michael@0 | 53 | ] |
michael@0 | 54 | ), |
michael@0 | 55 | 'continuation': ( |
michael@0 | 56 | 'Hello, \\\n world\nJellybeans!', [ |
michael@0 | 57 | ('Hello, \\\n world', 1), |
michael@0 | 58 | ('Jellybeans!', 3), |
michael@0 | 59 | ] |
michael@0 | 60 | ), |
michael@0 | 61 | 'multislash': ( |
michael@0 | 62 | 'Hello, \\\\\n world', [ |
michael@0 | 63 | ('Hello, \\\\', 1), |
michael@0 | 64 | (' world', 2), |
michael@0 | 65 | ] |
michael@0 | 66 | ) |
michael@0 | 67 | } |
michael@0 | 68 | |
michael@0 | 69 | def runSingle(self, s, lines): |
michael@0 | 70 | gotlines = [(d.s[d.lstart:d.lend], d.loc.line) for d in pymake.parser.enumeratelines(s, 'path')] |
michael@0 | 71 | self.assertEqual(gotlines, lines) |
michael@0 | 72 | |
michael@0 | 73 | multitest(LineEnumeratorTest) |
michael@0 | 74 | |
michael@0 | 75 | class IterTest(TestBase): |
michael@0 | 76 | testdata = { |
michael@0 | 77 | 'plaindata': ( |
michael@0 | 78 | pymake.parser.iterdata, |
michael@0 | 79 | "plaindata # test\n", |
michael@0 | 80 | "plaindata # test\n" |
michael@0 | 81 | ), |
michael@0 | 82 | 'makecomment': ( |
michael@0 | 83 | pymake.parser.itermakefilechars, |
michael@0 | 84 | "VAR = val # comment", |
michael@0 | 85 | "VAR = val " |
michael@0 | 86 | ), |
michael@0 | 87 | 'makeescapedcomment': ( |
michael@0 | 88 | pymake.parser.itermakefilechars, |
michael@0 | 89 | "VAR = val \# escaped hash", |
michael@0 | 90 | "VAR = val # escaped hash" |
michael@0 | 91 | ), |
michael@0 | 92 | 'makeescapedslash': ( |
michael@0 | 93 | pymake.parser.itermakefilechars, |
michael@0 | 94 | "VAR = val\\\\", |
michael@0 | 95 | "VAR = val\\\\", |
michael@0 | 96 | ), |
michael@0 | 97 | 'makecontinuation': ( |
michael@0 | 98 | pymake.parser.itermakefilechars, |
michael@0 | 99 | "VAR = VAL \\\n continuation # comment \\\n continuation", |
michael@0 | 100 | "VAR = VAL continuation " |
michael@0 | 101 | ), |
michael@0 | 102 | 'makecontinuation2': ( |
michael@0 | 103 | pymake.parser.itermakefilechars, |
michael@0 | 104 | "VAR = VAL \\ \\\n continuation", |
michael@0 | 105 | "VAR = VAL \\ continuation" |
michael@0 | 106 | ), |
michael@0 | 107 | 'makeawful': ( |
michael@0 | 108 | pymake.parser.itermakefilechars, |
michael@0 | 109 | "VAR = VAL \\\\# comment\n", |
michael@0 | 110 | "VAR = VAL \\" |
michael@0 | 111 | ), |
michael@0 | 112 | 'command': ( |
michael@0 | 113 | pymake.parser.itercommandchars, |
michael@0 | 114 | "echo boo # comment", |
michael@0 | 115 | "echo boo # comment", |
michael@0 | 116 | ), |
michael@0 | 117 | 'commandcomment': ( |
michael@0 | 118 | pymake.parser.itercommandchars, |
michael@0 | 119 | "echo boo \# comment", |
michael@0 | 120 | "echo boo \# comment", |
michael@0 | 121 | ), |
michael@0 | 122 | 'commandcontinue': ( |
michael@0 | 123 | pymake.parser.itercommandchars, |
michael@0 | 124 | "echo boo # \\\n\t command 2", |
michael@0 | 125 | "echo boo # \\\n command 2" |
michael@0 | 126 | ), |
michael@0 | 127 | } |
michael@0 | 128 | |
michael@0 | 129 | def runSingle(self, ifunc, idata, expected): |
michael@0 | 130 | d = pymake.parser.Data.fromstring(idata, 'IterTest data') |
michael@0 | 131 | |
michael@0 | 132 | it = pymake.parser._alltokens.finditer(d.s, 0, d.lend) |
michael@0 | 133 | actual = ''.join( [c for c, t, o, oo in ifunc(d, 0, ('dummy-token',), it)] ) |
michael@0 | 134 | self.assertEqual(actual, expected) |
michael@0 | 135 | |
michael@0 | 136 | if ifunc == pymake.parser.itermakefilechars: |
michael@0 | 137 | print "testing %r" % expected |
michael@0 | 138 | self.assertEqual(pymake.parser.flattenmakesyntax(d, 0), expected) |
michael@0 | 139 | |
michael@0 | 140 | multitest(IterTest) |
michael@0 | 141 | |
michael@0 | 142 | |
michael@0 | 143 | # 'define': ( |
michael@0 | 144 | # pymake.parser.iterdefinechars, |
michael@0 | 145 | # "endef", |
michael@0 | 146 | # "" |
michael@0 | 147 | # ), |
michael@0 | 148 | # 'definenesting': ( |
michael@0 | 149 | # pymake.parser.iterdefinechars, |
michael@0 | 150 | # """define BAR # comment |
michael@0 | 151 | #random text |
michael@0 | 152 | #endef not what you think! |
michael@0 | 153 | #endef # comment is ok\n""", |
michael@0 | 154 | # """define BAR # comment |
michael@0 | 155 | #random text |
michael@0 | 156 | #endef not what you think!""" |
michael@0 | 157 | # ), |
michael@0 | 158 | # 'defineescaped': ( |
michael@0 | 159 | # pymake.parser.iterdefinechars, |
michael@0 | 160 | # """value \\ |
michael@0 | 161 | #endef |
michael@0 | 162 | #endef\n""", |
michael@0 | 163 | # "value endef" |
michael@0 | 164 | # ), |
michael@0 | 165 | |
michael@0 | 166 | class MakeSyntaxTest(TestBase): |
michael@0 | 167 | # (string, startat, stopat, stopoffset, expansion |
michael@0 | 168 | testdata = { |
michael@0 | 169 | 'text': ('hello world', 0, (), None, ['hello world']), |
michael@0 | 170 | 'singlechar': ('hello $W', 0, (), None, |
michael@0 | 171 | ['hello ', |
michael@0 | 172 | {'type': 'VariableRef', |
michael@0 | 173 | '.vname': ['W']} |
michael@0 | 174 | ]), |
michael@0 | 175 | 'stopat': ('hello: world', 0, (':', '='), 6, ['hello']), |
michael@0 | 176 | 'funccall': ('h $(flavor FOO)', 0, (), None, |
michael@0 | 177 | ['h ', |
michael@0 | 178 | {'type': 'FlavorFunction', |
michael@0 | 179 | '[0]': ['FOO']} |
michael@0 | 180 | ]), |
michael@0 | 181 | 'escapedollar': ('hello$$world', 0, (), None, ['hello$world']), |
michael@0 | 182 | 'varref': ('echo $(VAR)', 0, (), None, |
michael@0 | 183 | ['echo ', |
michael@0 | 184 | {'type': 'VariableRef', |
michael@0 | 185 | '.vname': ['VAR']} |
michael@0 | 186 | ]), |
michael@0 | 187 | 'dynamicvarname': ('echo $($(VARNAME):.c=.o)', 0, (':',), None, |
michael@0 | 188 | ['echo ', |
michael@0 | 189 | {'type': 'SubstitutionRef', |
michael@0 | 190 | '.vname': [{'type': 'VariableRef', |
michael@0 | 191 | '.vname': ['VARNAME']} |
michael@0 | 192 | ], |
michael@0 | 193 | '.substfrom': ['.c'], |
michael@0 | 194 | '.substto': ['.o']} |
michael@0 | 195 | ]), |
michael@0 | 196 | 'substref': (' $(VAR:VAL) := $(VAL)', 0, (':=', '+=', '=', ':'), 15, |
michael@0 | 197 | [' ', |
michael@0 | 198 | {'type': 'VariableRef', |
michael@0 | 199 | '.vname': ['VAR:VAL']}, |
michael@0 | 200 | ' ']), |
michael@0 | 201 | 'vadsubstref': (' $(VAR:VAL) = $(VAL)', 15, (), None, |
michael@0 | 202 | [{'type': 'VariableRef', |
michael@0 | 203 | '.vname': ['VAL']}, |
michael@0 | 204 | ]), |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | def compareRecursive(self, actual, expected, path): |
michael@0 | 208 | self.assertEqual(len(actual), len(expected), |
michael@0 | 209 | "compareRecursive: %s %r" % (path, actual)) |
michael@0 | 210 | for i in xrange(0, len(actual)): |
michael@0 | 211 | ipath = path + [i] |
michael@0 | 212 | |
michael@0 | 213 | a, isfunc = actual[i] |
michael@0 | 214 | e = expected[i] |
michael@0 | 215 | if isinstance(e, str): |
michael@0 | 216 | self.assertEqual(a, e, "compareRecursive: %s" % (ipath,)) |
michael@0 | 217 | else: |
michael@0 | 218 | self.assertEqual(type(a), getattr(pymake.functions, e['type']), |
michael@0 | 219 | "compareRecursive: %s" % (ipath,)) |
michael@0 | 220 | for k, v in e.iteritems(): |
michael@0 | 221 | if k == 'type': |
michael@0 | 222 | pass |
michael@0 | 223 | elif k[0] == '[': |
michael@0 | 224 | item = int(k[1:-1]) |
michael@0 | 225 | proppath = ipath + [item] |
michael@0 | 226 | self.compareRecursive(a[item], v, proppath) |
michael@0 | 227 | elif k[0] == '.': |
michael@0 | 228 | item = k[1:] |
michael@0 | 229 | proppath = ipath + [item] |
michael@0 | 230 | self.compareRecursive(getattr(a, item), v, proppath) |
michael@0 | 231 | else: |
michael@0 | 232 | raise Exception("Unexpected property at %s: %s" % (ipath, k)) |
michael@0 | 233 | |
michael@0 | 234 | def runSingle(self, s, startat, stopat, stopoffset, expansion): |
michael@0 | 235 | d = pymake.parser.Data.fromstring(s, pymake.parserdata.Location('testdata', 1, 0)) |
michael@0 | 236 | |
michael@0 | 237 | a, t, offset = pymake.parser.parsemakesyntax(d, startat, stopat, pymake.parser.itermakefilechars) |
michael@0 | 238 | self.compareRecursive(a, expansion, []) |
michael@0 | 239 | self.assertEqual(offset, stopoffset) |
michael@0 | 240 | |
michael@0 | 241 | multitest(MakeSyntaxTest) |
michael@0 | 242 | |
michael@0 | 243 | class VariableTest(TestBase): |
michael@0 | 244 | testdata = """ |
michael@0 | 245 | VAR = value |
michael@0 | 246 | VARNAME = TESTVAR |
michael@0 | 247 | $(VARNAME) = testvalue |
michael@0 | 248 | $(VARNAME:VAR=VAL) = moretesting |
michael@0 | 249 | IMM := $(VARNAME) # this is a comment |
michael@0 | 250 | MULTIVAR = val1 \\ |
michael@0 | 251 | val2 |
michael@0 | 252 | VARNAME = newname |
michael@0 | 253 | """ |
michael@0 | 254 | expected = {'VAR': 'value', |
michael@0 | 255 | 'VARNAME': 'newname', |
michael@0 | 256 | 'TESTVAR': 'testvalue', |
michael@0 | 257 | 'TESTVAL': 'moretesting', |
michael@0 | 258 | 'IMM': 'TESTVAR ', |
michael@0 | 259 | 'MULTIVAR': 'val1 val2', |
michael@0 | 260 | 'UNDEF': None} |
michael@0 | 261 | |
michael@0 | 262 | def runTest(self): |
michael@0 | 263 | stmts = pymake.parser.parsestring(self.testdata, 'VariableTest') |
michael@0 | 264 | |
michael@0 | 265 | m = pymake.data.Makefile() |
michael@0 | 266 | stmts.execute(m) |
michael@0 | 267 | for k, v in self.expected.iteritems(): |
michael@0 | 268 | flavor, source, val = m.variables.get(k) |
michael@0 | 269 | if val is None: |
michael@0 | 270 | self.assertEqual(val, v, 'variable named %s' % k) |
michael@0 | 271 | else: |
michael@0 | 272 | self.assertEqual(val.resolvestr(m, m.variables), v, 'variable named %s' % k) |
michael@0 | 273 | |
michael@0 | 274 | class SimpleRuleTest(TestBase): |
michael@0 | 275 | testdata = """ |
michael@0 | 276 | VAR = value |
michael@0 | 277 | TSPEC = dummy |
michael@0 | 278 | all: TSPEC = myrule |
michael@0 | 279 | all:: test test2 $(VAR) |
michael@0 | 280 | echo "Hello, $(TSPEC)" |
michael@0 | 281 | |
michael@0 | 282 | %.o: %.c |
michael@0 | 283 | $(CC) -o $@ $< |
michael@0 | 284 | """ |
michael@0 | 285 | |
michael@0 | 286 | def runTest(self): |
michael@0 | 287 | stmts = pymake.parser.parsestring(self.testdata, 'SimpleRuleTest') |
michael@0 | 288 | |
michael@0 | 289 | m = pymake.data.Makefile() |
michael@0 | 290 | stmts.execute(m) |
michael@0 | 291 | self.assertEqual(m.defaulttarget, 'all', "Default target") |
michael@0 | 292 | |
michael@0 | 293 | self.assertTrue(m.hastarget('all'), "Has 'all' target") |
michael@0 | 294 | target = m.gettarget('all') |
michael@0 | 295 | rules = target.rules |
michael@0 | 296 | self.assertEqual(len(rules), 1, "Number of rules") |
michael@0 | 297 | prereqs = rules[0].prerequisites |
michael@0 | 298 | self.assertEqual(prereqs, ['test', 'test2', 'value'], "Prerequisites") |
michael@0 | 299 | commands = rules[0].commands |
michael@0 | 300 | self.assertEqual(len(commands), 1, "Number of commands") |
michael@0 | 301 | expanded = commands[0].resolvestr(m, target.variables) |
michael@0 | 302 | self.assertEqual(expanded, 'echo "Hello, myrule"') |
michael@0 | 303 | |
michael@0 | 304 | irules = m.implicitrules |
michael@0 | 305 | self.assertEqual(len(irules), 1, "Number of implicit rules") |
michael@0 | 306 | |
michael@0 | 307 | irule = irules[0] |
michael@0 | 308 | self.assertEqual(len(irule.targetpatterns), 1, "%.o target pattern count") |
michael@0 | 309 | self.assertEqual(len(irule.prerequisites), 1, "%.o prerequisite count") |
michael@0 | 310 | self.assertEqual(irule.targetpatterns[0].match('foo.o'), 'foo', "%.o stem") |
michael@0 | 311 | |
michael@0 | 312 | if __name__ == '__main__': |
michael@0 | 313 | logging.basicConfig(level=logging.DEBUG) |
michael@0 | 314 | unittest.main() |