• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1## This file is part of Scapy
2## See http://www.secdev.org/projects/scapy for more informations
3## Copyright (C) Philippe Biondi <phil@secdev.org>
4## This program is published under a GPLv2 license
5
6"""
7Unit testing infrastructure for Scapy
8"""
9
10from __future__ import absolute_import
11from __future__ import print_function
12import sys, getopt, imp, glob, importlib
13import hashlib, copy, bz2, base64, os.path, time, traceback, zlib
14from scapy.consts import WINDOWS
15import scapy.modules.six as six
16from scapy.modules.six.moves import range
17
18
19### Util class ###
20
21class Bunch:
22    __init__ = lambda self, **kw: setattr(self, '__dict__', kw)
23
24#### Import tool ####
25
26def import_module(name):
27    name = os.path.realpath(name)
28    thepath = os.path.dirname(name)
29    name = os.path.basename(name)
30    if name.endswith(".py"):
31        name = name[:-3]
32    f,path,desc = imp.find_module(name,[thepath])
33
34    try:
35        return imp.load_module(name, f, path, desc)
36    finally:
37        if f:
38            f.close()
39
40
41#### INTERNAL/EXTERNAL FILE EMBEDDING ####
42
43class File:
44    def __init__(self, name, URL, local):
45        self.name = name
46        self.local = local.encode("utf8")
47        self.URL = URL
48    def get_local(self):
49        return bz2.decompress(base64.decodestring(self.local))
50    def get_URL(self):
51        return self.URL
52    def write(self, dir):
53        if dir:
54            dir += "/"
55        open(dir+self.name,"wb").write(self.get_local())
56
57
58# Embed a base64 encoded bziped version of js and css files
59# to work if you can't reach Internet.
60class External_Files:
61    UTscapy_js = File("UTscapy.js", "http://www.secdev.org/projects/UTscapy/UTscapy.js",
62"""QlpoOTFBWSZTWWVijKQAAXxfgERUYOvAChIhBAC/79+qQAH8AFA0poANAMjQAAAG
63ABo0NGEZNBo00BhgAaNDRhGTQaNNAYFURJinplGaKbRkJiekzSenqmpA0Gm1LFMp
64RUklVQlK9WUTZYpNFI1IiEWEFT09Sfj5uO+qO6S5DQwKIxM92+Zku94wL6V/1KTK
65an2c66Ug6SmVKy1ZIrgauxMVLF5xLH0lJRQuKlqLF10iatlTzqvw7S9eS3+h4lu3
66GZyMgoOude3NJ1pQy8eo+X96IYZw+ynehsiPj73m0rnvQ3QXZ9BJQiZQYQ5/uNcl
672WOlC5vyQqV/BWsnr2NZYLYXQLDs/Bffk4ZfR4/SH6GfA5Xlek4xHNHqbSsRbREO
68gueXo3kcYi94K6hSO3ldD2O/qJXOFqJ8o3TE2aQahxtQpCVUKQMvODHwu2YkaORY
69ZC6gihEallcHDIAtRPScBACAJnUggYhLDX6DEko7nC9GvAw5OcEkiyDUbLdiGCzD
70aXWMC2DuQ2Y6sGf6NcRuON7QSbhHsPc4KKmZ/xdyRThQkGVijKQ=""")
71    UTscapy_css = File("UTscapy.css","http://www.secdev.org/projects/UTscapy/UTscapy.css",
72"""QlpoOTFBWSZTWTbBCNEAAE7fgHxwSB//+Cpj2QC//9/6UAR+63dxbNzO3ccmtGEk
73pM0m1I9E/Qp6g9Q09TNQ9QDR6gMgAkiBFG9U9TEGRkGgABoABoBmpJkRAaAxD1AN
74Gh6gNADQBzAATJgATCYJhDAEYAEiQkwIyJk0n6qenpqeoaMUeo9RgIxp6pX78kfx
75Jx4MUhDHKEb2pJAYAelG1cybiZBBDipH8ocxNyHDAqTUxiQmIAEDE3ApIBUUECAT
767Lvlf4xA/sVK0QHkSlYtT0JmErdOjx1v5NONPYSjrIhQnbl1MbG5m+InMYmVAWJp
77uklD9cNdmQv2YigxbEtgUrsY2pDDV/qMT2SHnHsViu2rrp2LA01YJIHZqjYCGIQN
78sGNobFxAYHLqqMOj9TI2Y4GRpRCUGu82PnMnXUBgDSkTY4EfmygaqvUwbGMbPwyE
79220Q4G+sDvw7+6in3CAOS634pcOEAdREUW+QqMjvWvECrGISo1piv3vqubTGOL1c
80ssrFnnSfU4T6KSCbPs98HJ2yjWN4i8Bk5WrM/JmELLNeZ4vgMkA4JVQInNnWTUTe
81gmMSlJd/b7JuRwiM5RUzXOBTa0e3spO/rsNJiylu0rCxygdRo2koXdSJzmUVjJUm
82BOFIkUKq8LrE+oT9h2qUqqUQ25fGV7e7OFkpmZopqUi0WeIBzlXdYY0Zz+WUJUTC
83RC+CIPFIYh1RkopswMAop6ZjuZKRqR0WNuV+rfuF5aCXPpxAm0F14tPyhf42zFMT
84GJUMxxowJnoauRq4xGQk+2lYFxbQ0FiC43WZSyYLHMuo5NTJ92QLAgs4FgOyZQqQ
85xpsGKMA0cIisNeiootpnlWQvkPzNGUTPg8jqkwTvqQLguZLKJudha1hqfBib1IfO
86LNChcU6OqF+3wyPKg5Y5oSbSJPAMcRDANwmS2i9oZm6vsD1pLkWtFGbAkEjjCuEU
87W1ev1IsF2UVmWYFtJkqLT708ApUBK/ig3rbJWSq7RGQd3sSrOKu3lyKzTBdkXK2a
88BGLV5dS1XURdKxaRkMplLLQxsimBYZEAa8KQkYyI+4EagMqycRR7RgwtZFxJSu0T
891q5wS2JG82iETHplbNj8DYo9IkmKzNAiw4FxK8bRfIYvwrbshbEagL11AQJFsqeZ
90WeXDoWEx2FMyyZRAB5QyCFnwYtwtWAQmmITY8aIM2SZyRnHH9Wi8+Sr2qyCscFYo
91vzM985aHXOHAxQN2UQZbQkUv3D4Vc+lyvalAffv3Tyg4ks3a22kPXiyeCGweviNX
920K8TKasyOhGsVamTUAZBXfQVw1zmdS4rHDnbHgtIjX3DcCt6UIr0BHTYjdV0JbPj
93r1APYgXihjQwM2M83AKIhwQQJv/F3JFOFCQNsEI0QA==""")
94    def get_local_dict(cls):
95        return {x: y.name for (x, y) in six.iteritems(cls.__dict__)
96                if isinstance(y, File)}
97    get_local_dict = classmethod(get_local_dict)
98    def get_URL_dict(cls):
99        return {x: y.URL for (x, y) in six.iteritems(cls.__dict__)
100                if isinstance(y, File)}
101    get_URL_dict = classmethod(get_URL_dict)
102
103
104#### HELPER CLASSES FOR PARAMETRING OUTPUT FORMAT ####
105
106class EnumClass:
107    def from_string(cls,x):
108        return cls.__dict__[x.upper()]
109    from_string = classmethod(from_string)
110
111class Format(EnumClass):
112    TEXT  = 1
113    ANSI  = 2
114    HTML  = 3
115    LATEX = 4
116    XUNIT = 5
117
118
119#### TEST CLASSES ####
120
121class TestClass:
122    def __getitem__(self, item):
123        return getattr(self, item)
124    def add_keywords(self, kws):
125        if isinstance(kws, six.string_types):
126            kws = [kws]
127        for kwd in kws:
128            if kwd.startswith('-'):
129                try:
130                    self.keywords.remove(kwd[1:])
131                except KeyError:
132                    pass
133            else:
134                self.keywords.add(kwd)
135
136class TestCampaign(TestClass):
137    def __init__(self, title):
138        self.title = title
139        self.filename = None
140        self.headcomments = ""
141        self.campaign = []
142        self.keywords = set()
143        self.crc = None
144        self.sha = None
145        self.preexec = None
146        self.preexec_output = None
147        self.end_pos = 0
148    def add_testset(self, testset):
149        self.campaign.append(testset)
150        testset.keywords.update(self.keywords)
151    def startNum(self, beginpos):
152        for ts in self:
153            for t in ts:
154                t.num = beginpos
155                beginpos += 1
156        self.end_pos = beginpos
157    def __iter__(self):
158        return self.campaign.__iter__()
159    def all_tests(self):
160        for ts in self:
161            for t in ts:
162                yield t
163
164class TestSet(TestClass):
165    def __init__(self, name):
166        self.name = name
167        self.tests = []
168        self.comments = ""
169        self.keywords = set()
170        self.crc = None
171        self.expand = 1
172    def add_test(self, test):
173        self.tests.append(test)
174        test.keywords.update(self.keywords)
175    def __iter__(self):
176        return self.tests.__iter__()
177
178class UnitTest(TestClass):
179    def __init__(self, name):
180        self.name = name
181        self.test = ""
182        self.comments = ""
183        self.result = ""
184        self.res = True  # must be True at init to have a different truth value than None
185        self.output = ""
186        self.num = -1
187        self.keywords = set()
188        self.crc = None
189        self.expand = 1
190    def decode(self):
191        if six.PY2:
192            self.test = self.test.decode("utf8", "ignore")
193            self.output = self.output.decode("utf8", "ignore")
194            self.comments = self.comments.decode("utf8", "ignore")
195            self.result = self.result.decode("utf8", "ignore")
196    def __nonzero__(self):
197        return self.res
198    __bool__ = __nonzero__
199
200
201# Careful note: all data not included will be set by default.
202# Use -c as first argument !!
203def parse_config_file(config_path, verb=3):
204    """Parse provided json to get configuration
205    Empty default json:
206    {
207      "testfiles": [],
208      "onlyfailed": false,
209      "verb": 2,
210      "dump": 0,
211      "crc": true,
212      "scapy": "scapy",
213      "preexec": {},
214      "global_preexec": "",
215      "outputfile": null,
216      "local": true,
217      "format": "ansi",
218      "num": null,
219      "modules": [],
220      "kw_ok": [],
221      "kw_ko": []
222    }
223
224    """
225    import json, unicodedata
226    with open(config_path) as config_file:
227        data = json.load(config_file, encoding="utf8")
228        if verb > 2:
229            print("### Loaded config file", config_path, file=sys.stderr)
230    def get_if_exist(key, default):
231        return data[key] if key in data else default
232    return Bunch(testfiles=get_if_exist("testfiles", []), onlyfailed=get_if_exist("onlyfailed", False),
233                 verb=get_if_exist("verb", 3), dump=get_if_exist("dump", 0), crc=get_if_exist("crc", 1),
234                 scapy=get_if_exist("scapy", "scapy"), preexec=get_if_exist("preexec", {}),
235                 global_preexec=get_if_exist("global_preexec", ""), outfile=get_if_exist("outputfile", sys.stdout),
236                 local=get_if_exist("local", 0), num=get_if_exist("num", None), modules=get_if_exist("modules", []),
237                 kw_ok=get_if_exist("kw_ok", []), kw_ko=get_if_exist("kw_ko", []), format=get_if_exist("format", "ansi"))
238
239#### PARSE CAMPAIGN ####
240
241def parse_campaign_file(campaign_file):
242    test_campaign = TestCampaign("Test campaign")
243    test_campaign.filename=  campaign_file.name
244    testset = None
245    test = None
246    testnb = 0
247
248    for l in campaign_file.readlines():
249        if l[0] == '#':
250            continue
251        if l[0] == "~":
252            (test or testset or test_campaign).add_keywords(l[1:].split())
253        elif l[0] == "%":
254            test_campaign.title = l[1:].strip()
255        elif l[0] == "+":
256            testset = TestSet(l[1:].strip())
257            test_campaign.add_testset(testset)
258            test = None
259        elif l[0] == "=":
260            test = UnitTest(l[1:].strip())
261            test.num = testnb
262            testnb += 1
263            testset.add_test(test)
264        elif l[0] == "*":
265            if test is not None:
266                test.comments += l[1:]
267            elif testset is not None:
268                testset.comments += l[1:]
269            else:
270                test_campaign.headcomments += l[1:]
271        else:
272            if test is None:
273                if l.strip():
274                    print("Unknown content [%s]" % l.strip(), file=sys.stderr)
275            else:
276                test.test += l
277    return test_campaign
278
279def dump_campaign(test_campaign):
280    print("#"*(len(test_campaign.title)+6))
281    print("## %(title)s ##" % test_campaign)
282    print("#"*(len(test_campaign.title)+6))
283    if test_campaign.sha and test_campaign.crc:
284        print("CRC=[%(crc)s] SHA=[%(sha)s]" % test_campaign)
285    print("from file %(filename)s" % test_campaign)
286    print()
287    for ts in test_campaign:
288        if ts.crc:
289            print("+--[%s]%s(%s)--" % (ts.name,"-"*max(2,80-len(ts.name)-18),ts.crc))
290        else:
291            print("+--[%s]%s" % (ts.name,"-"*max(2,80-len(ts.name)-6)))
292        if ts.keywords:
293            print("  kw=%s" % ",".join(ts.keywords))
294        for t in ts:
295            print("%(num)03i %(name)s" % t)
296            c = k = ""
297            if t.keywords:
298                k = "kw=%s" % ",".join(t.keywords)
299            if t.crc:
300                c = "[%(crc)s] " % t
301            if c or k:
302                print("    %s%s" % (c,k))
303
304#### COMPUTE CAMPAIGN DIGESTS ####
305if six.PY2:
306    def crc32(x):
307        return "%08X" % (0xffffffff & zlib.crc32(x))
308
309    def sha1(x):
310         return hashlib.sha1(x).hexdigest().upper()
311else:
312    def crc32(x):
313        return "%08X" % (0xffffffff & zlib.crc32(bytearray(x, "utf8")))
314
315    def sha1(x):
316        return hashlib.sha1(x.encode("utf8")).hexdigest().upper()
317
318def compute_campaign_digests(test_campaign):
319    dc = ""
320    for ts in test_campaign:
321        dts = ""
322        for t in ts:
323            dt = t.test.strip()
324            t.crc = crc32(dt)
325            dts += "\0"+dt
326        ts.crc = crc32(dts)
327        dc += "\0\x01"+dts
328    test_campaign.crc = crc32(dc)
329    test_campaign.sha = sha1(open(test_campaign.filename).read())
330
331
332#### FILTER CAMPAIGN #####
333
334def filter_tests_on_numbers(test_campaign, num):
335    if num:
336        for ts in test_campaign:
337            ts.tests = [t for t in ts.tests if t.num in num]
338        test_campaign.campaign = [ts for ts in test_campaign.campaign
339                                  if ts.tests]
340
341def filter_tests_keep_on_keywords(test_campaign, kw):
342    def kw_match(lst, kw):
343        for k in lst:
344            if k in kw:
345                return True
346        return False
347
348    if kw:
349        for ts in test_campaign:
350            ts.tests = [t for t in ts.tests if kw_match(t.keywords, kw)]
351
352def filter_tests_remove_on_keywords(test_campaign, kw):
353    def kw_match(lst, kw):
354        for k in kw:
355            if k in lst:
356                return True
357        return False
358
359    if kw:
360        for ts in test_campaign:
361            ts.tests = [t for t in ts.tests if not kw_match(t.keywords, kw)]
362
363
364def remove_empty_testsets(test_campaign):
365    test_campaign.campaign = [ts for ts in test_campaign.campaign if ts.tests]
366
367
368#### RUN CAMPAIGN #####
369
370def run_campaign(test_campaign, get_interactive_session, verb=3, ignore_globals=None):
371    passed=failed=0
372    if test_campaign.preexec:
373        test_campaign.preexec_output = get_interactive_session(test_campaign.preexec.strip(), ignore_globals=ignore_globals)[0]
374    for testset in test_campaign:
375        for t in testset:
376            t.output,res = get_interactive_session(t.test.strip(), ignore_globals=ignore_globals)
377            the_res = False
378            try:
379                if res is None or res:
380                    the_res= True
381            except Exception as msg:
382                t.output+="UTscapy: Error during result interpretation:\n"
383                t.output+="".join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2],))
384            if the_res:
385                t.res = True
386                res = "passed"
387                passed += 1
388            else:
389                t.res = False
390                res = "failed"
391                failed += 1
392            t.result = res
393            t.decode()
394            if verb > 1:
395                print("%(result)6s %(crc)s %(name)s" % t, file=sys.stderr)
396    test_campaign.passed = passed
397    test_campaign.failed = failed
398    if verb:
399        print("Campaign CRC=%(crc)s  SHA=%(sha)s" % test_campaign, file=sys.stderr)
400        print("PASSED=%i FAILED=%i" % (passed, failed), file=sys.stderr)
401    return failed
402
403
404#### INFO LINES ####
405
406def info_line(test_campaign):
407    filename = test_campaign.filename
408    if filename is None:
409        return "Run %s by UTscapy" % time.ctime()
410    else:
411        return "Run %s from [%s] by UTscapy" % (time.ctime(), filename)
412
413def html_info_line(test_campaign):
414    filename = test_campaign.filename
415    if filename is None:
416        return """Run %s by <a href="http://www.secdev.org/projects/UTscapy/">UTscapy</a><br>""" % time.ctime()
417    else:
418        return """Run %s from [%s] by <a href="http://www.secdev.org/projects/UTscapy/">UTscapy</a><br>""" % (time.ctime(), filename)
419
420
421#### CAMPAIGN TO something ####
422
423def campaign_to_TEXT(test_campaign):
424    output="%(title)s\n" % test_campaign
425    output += "-- "+info_line(test_campaign)+"\n\n"
426    output += "Passed=%(passed)i\nFailed=%(failed)i\n\n%(headcomments)s\n" % test_campaign
427
428    for testset in test_campaign:
429        if any(t.expand for t in testset):
430            output += "######\n## %(name)s\n######\n%(comments)s\n\n" % testset
431            for t in testset:
432                if t.expand:
433                    output += "###(%(num)03i)=[%(result)s] %(name)s\n%(comments)s\n%(output)s\n\n" % t
434
435    return output
436
437def campaign_to_ANSI(test_campaign):
438    output="%(title)s\n" % test_campaign
439    output += "-- "+info_line(test_campaign)+"\n\n"
440    output += "Passed=%(passed)i\nFailed=%(failed)i\n\n%(headcomments)s\n" % test_campaign
441
442    for testset in test_campaign:
443        if any(t.expand for t in testset):
444            output += "######\n## %(name)s\n######\n%(comments)s\n\n" % testset
445            for t in testset:
446                if t.expand:
447                    output += "###(%(num)03i)=[%(result)s] %(name)s\n%(comments)s\n%(output)s\n\n" % t
448
449    return output
450
451def campaign_to_xUNIT(test_campaign):
452    output='<?xml version="1.0" encoding="UTF-8" ?>\n<testsuite>\n'
453    for testset in test_campaign:
454        for t in testset:
455            output += ' <testcase classname="%s"\n' % testset.name.encode("string_escape").replace('"',' ')
456            output += '           name="%s"\n' % t.name.encode("string_escape").replace('"',' ')
457            output += '           duration="0">\n' % t
458            if not t.res:
459                output += '<error><![CDATA[%(output)s]]></error>\n' % t
460            output += "</testcase>\n"
461    output += '</testsuite>'
462    return output
463
464
465def campaign_to_HTML(test_campaign):
466    output = """
467<h1>%(title)s</h1>
468
469<p>
470""" % test_campaign
471
472    if test_campaign.crc is not None and test_campaign.sha is not None:
473        output += "CRC=<span class=crc>%(crc)s</span> SHA=<span class=crc>%(sha)s</span><br>" % test_campaign
474    output += "<small><em>"+html_info_line(test_campaign)+"</em></small>"
475    output += test_campaign.headcomments +  "\n<p>PASSED=%(passed)i FAILED=%(failed)i<p>\n\n" % test_campaign
476
477    for testset in test_campaign:
478        output += "<h2>" % testset
479        if testset.crc is not None:
480            output += "<span class=crc>%(crc)s</span> " % testset
481        output += "%(name)s</h2>\n%(comments)s\n<ul>\n" % testset
482        for t in testset:
483            output += """<li class=%(result)s id="tst%(num)il">\n""" % t
484            if t.expand == 2:
485                output +="""
486<span id="tst%(num)i+" class="button%(result)s" onClick="show('tst%(num)i')" style="POSITION: absolute; VISIBILITY: hidden;">+%(num)03i+</span>
487<span id="tst%(num)i-" class="button%(result)s" onClick="hide('tst%(num)i')">-%(num)03i-</span>
488""" % t
489            else:
490                output += """
491<span id="tst%(num)i+" class="button%(result)s" onClick="show('tst%(num)i')">+%(num)03i+</span>
492<span id="tst%(num)i-" class="button%(result)s" onClick="hide('tst%(num)i')" style="POSITION: absolute; VISIBILITY: hidden;">-%(num)03i-</span>
493""" % t
494            if t.crc is not None:
495                output += "<span class=crc>%(crc)s</span>\n" % t
496            output += """%(name)s\n<span class="comment %(result)s" id="tst%(num)i" """ % t
497            if t.expand < 2:
498                output += """ style="POSITION: absolute; VISIBILITY: hidden;" """
499            output += """><br>%(comments)s
500<pre>
501%(output)s</pre></span>
502""" % t
503        output += "\n</ul>\n\n"
504    return output
505
506def pack_html_campaigns(runned_campaigns, data, local=0, title=None):
507    output = """
508<html>
509<head>
510<title>%(title)s</title>
511<h1>UTScapy tests</h1>
512
513<span class=button onClick="hide_all('tst')">Shrink All</span>
514<span class=button onClick="show_all('tst')">Expand All</span>
515<span class=button onClick="show_passed('tst')">Expand Passed</span>
516<span class=button onClick="show_failed('tst')">Expand Failed</span>
517
518<p>
519"""
520    for test_campaign in runned_campaigns:
521        for ts in test_campaign:
522            for t in ts:
523                output += """<span class=button%(result)s onClick="goto_id('tst%(num)il')">%(num)03i</span>\n""" % t
524
525    output += """</p>\n\n
526<link rel="stylesheet" href="%(UTscapy_css)s" type="text/css">
527<script language="JavaScript" src="%(UTscapy_js)s" type="text/javascript"></script>
528</head>
529<body>
530%(data)s
531</body></html>
532"""
533    out_dict = {'data': data, 'title': title if title else "UTScapy tests"}
534    if local:
535        External_Files.UTscapy_js.write(os.path.dirname(test_campaign.output_file.name))
536        External_Files.UTscapy_css.write(os.path.dirname(test_campaign.output_file.name))
537        out_dict.update(External_Files.get_local_dict())
538    else:
539        out_dict.update(External_Files.get_URL_dict())
540
541    output %= out_dict
542    return output
543
544def campaign_to_LATEX(test_campaign):
545    output = r"""\documentclass{report}
546\usepackage{alltt}
547\usepackage{xcolor}
548\usepackage{a4wide}
549\usepackage{hyperref}
550
551\title{%(title)s}
552\date{%%s}
553
554\begin{document}
555\maketitle
556\tableofcontents
557
558\begin{description}
559\item[Passed:] %(passed)i
560\item[Failed:] %(failed)i
561\end{description}
562
563%(headcomments)s
564
565""" % test_campaign
566    output %= info_line(test_campaign)
567
568    for testset in test_campaign:
569        output += "\\chapter{%(name)s}\n\n%(comments)s\n\n" % testset
570        for t in testset:
571            if t.expand:
572                output += r"""\section{%(name)s}
573
574[%(num)03i] [%(result)s]
575
576%(comments)s
577\begin{alltt}
578%(output)s
579\end{alltt}
580
581""" % t
582
583    output += "\\end{document}\n"
584    return output
585
586
587
588#### USAGE ####
589
590def usage():
591    print("""Usage: UTscapy [-m module] [-f {text|ansi|HTML|LaTeX}] [-o output_file]
592               [-t testfile] [-T testfile] [-k keywords [-k ...]] [-K keywords [-K ...]]
593               [-l] [-d|-D] [-F] [-q[q]] [-P preexecute_python_code]
594               [-s /path/to/scapy] [-c configfile]
595-t\t\t: provide test files (can be used many times)
596-T\t\t: if -t is used with *, remove a specific file (can be used many times)
597-l\t\t: generate local files
598-F\t\t: expand only failed tests
599-d\t\t: dump campaign
600-D\t\t: dump campaign and stop
601-C\t\t: don't calculate CRC and SHA
602-s\t\t: path to scapy.py
603-c\t\t: load a .utsc config file
604-q\t\t: quiet mode
605-qq\t\t: [silent mode]
606-n <testnum>\t: only tests whose numbers are given (eg. 1,3-7,12)
607-m <module>\t: additional module to put in the namespace
608-k <kw1>,<kw2>,...\t: include only tests with one of those keywords (can be used many times)
609-K <kw1>,<kw2>,...\t: remove tests with one of those keywords (can be used many times)
610-P <preexecute_python_code>
611""", file=sys.stderr)
612    raise SystemExit
613
614
615#### MAIN ####
616
617def execute_campaign(TESTFILE, OUTPUTFILE, PREEXEC, NUM, KW_OK, KW_KO, DUMP,
618                     FORMAT, VERB, ONLYFAILED, CRC, autorun_func, pos_begin=0, ignore_globals=None):
619    # Parse test file
620    test_campaign = parse_campaign_file(TESTFILE)
621
622    # Report parameters
623    if PREEXEC:
624        test_campaign.preexec = PREEXEC
625
626    # Compute campaign CRC and SHA
627    if CRC:
628        compute_campaign_digests(test_campaign)
629
630    # Filter out unwanted tests
631    filter_tests_on_numbers(test_campaign, NUM)
632    for k in KW_OK:
633        filter_tests_keep_on_keywords(test_campaign, k)
634    for k in KW_KO:
635        filter_tests_remove_on_keywords(test_campaign, k)
636
637    remove_empty_testsets(test_campaign)
638
639
640    # Dump campaign
641    if DUMP:
642        dump_campaign(test_campaign)
643        if DUMP > 1:
644            sys.exit()
645
646    # Run tests
647    test_campaign.output_file = OUTPUTFILE
648    result = run_campaign(test_campaign, autorun_func[FORMAT], verb=VERB, ignore_globals=None)
649
650    # Shrink passed
651    if ONLYFAILED:
652        for t in test_campaign.all_tests():
653            if t:
654                t.expand = 0
655            else:
656                t.expand = 2
657
658    pos_end = 0
659    # Generate report
660    if FORMAT == Format.TEXT:
661        output = campaign_to_TEXT(test_campaign)
662    elif FORMAT == Format.ANSI:
663        output = campaign_to_ANSI(test_campaign)
664    elif FORMAT == Format.HTML:
665        test_campaign.startNum(pos_begin)
666        output = campaign_to_HTML(test_campaign)
667    elif FORMAT == Format.LATEX:
668        output = campaign_to_LATEX(test_campaign)
669    elif FORMAT == Format.XUNIT:
670        output = campaign_to_xUNIT(test_campaign)
671
672    return output, (result == 0), test_campaign
673
674def resolve_testfiles(TESTFILES):
675    for tfile in TESTFILES[:]:
676        if "*" in tfile:
677            TESTFILES.remove(tfile)
678            TESTFILES.extend(glob.glob(tfile))
679    return TESTFILES
680
681def main(argv):
682    ignore_globals = list(six.moves.builtins.__dict__.keys())
683
684    # Parse arguments
685
686    FORMAT = Format.ANSI
687    TESTFILE = sys.stdin
688    OUTPUTFILE = sys.stdout
689    LOCAL = 0
690    NUM = None
691    KW_OK = []
692    KW_KO = []
693    DUMP = 0
694    CRC = True
695    ONLYFAILED = False
696    VERB = 3
697    GLOB_PREEXEC = ""
698    PREEXEC_DICT = {}
699    SCAPY = "scapy"
700    MODULES = []
701    TESTFILES = []
702    try:
703        opts = getopt.getopt(argv, "o:t:T:c:f:hln:m:k:K:DdCFqP:s:")
704        for opt,optarg in opts[0]:
705            if opt == "-h":
706                usage()
707            elif opt == "-F":
708                ONLYFAILED = True
709            elif opt == "-q":
710                VERB -= 1
711            elif opt == "-D":
712                DUMP = 2
713            elif opt == "-d":
714                DUMP = 1
715            elif opt == "-C":
716                CRC = False
717            elif opt == "-s":
718                SCAPY = optarg
719            elif opt == "-P":
720                GLOB_PREEXEC += "\n"+optarg
721            elif opt == "-f":
722                try:
723                    FORMAT = Format.from_string(optarg)
724                except KeyError as msg:
725                    raise getopt.GetoptError("Unknown output format %s" % msg)
726            elif opt == "-t":
727                TESTFILES.append(optarg)
728                TESTFILES = resolve_testfiles(TESTFILES)
729            elif opt == "-T":
730                TESTFILES.remove(optarg)
731            elif opt == "-c":
732                data = parse_config_file(optarg, VERB)
733                ONLYFAILED = data.onlyfailed
734                VERB = data.verb
735                DUMP = data.dump
736                CRC = data.crc
737                SCAPY = data.scapy
738                PREEXEC_DICT = data.preexec
739                GLOB_PREEXEC = data.global_preexec
740                OUTPUTFILE = data.outfile
741                TESTFILES = data.testfiles
742                LOCAL = 1 if data.local else 0
743                NUM = data.num
744                MODULES = data.modules
745                KW_OK = [data.kw_ok]
746                KW_KO = [data.kw_ko]
747                try:
748                    FORMAT = Format.from_string(data.format)
749                except KeyError as msg:
750                    raise getopt.GetoptError("Unknown output format %s" % msg)
751                TESTFILES = resolve_testfiles(TESTFILES)
752            elif opt == "-o":
753                OUTPUTFILE = open(optarg, "wb")
754            elif opt == "-l":
755                LOCAL = 1
756            elif opt == "-n":
757                NUM = []
758                for v in (x.strip() for x in optarg.split(",")):
759                    try:
760                        NUM.append(int(v))
761                    except ValueError:
762                        v1, v2 = [int(e) for e in v.split('-', 1)]
763                        NUM.extend(range(v1, v2 + 1))
764            elif opt == "-m":
765                MODULES.append(optarg)
766            elif opt == "-k":
767                KW_OK.append(optarg.split(","))
768            elif opt == "-K":
769                KW_KO.append(optarg.split(","))
770
771        if VERB > 2:
772            print("### Booting scapy...", file=sys.stderr)
773        try:
774            from scapy import all as scapy
775        except ImportError as e:
776            raise getopt.GetoptError("cannot import [%s]: %s" % (SCAPY,e))
777
778        for m in MODULES:
779            try:
780                mod = import_module(m)
781                six.moves.builtins.__dict__.update(mod.__dict__)
782            except ImportError as e:
783                raise getopt.GetoptError("cannot import [%s]: %s" % (m,e))
784
785    except getopt.GetoptError as msg:
786        print("ERROR:",msg, file=sys.stderr)
787        raise SystemExit
788
789    autorun_func = {
790        Format.TEXT: scapy.autorun_get_text_interactive_session,
791        Format.ANSI: scapy.autorun_get_ansi_interactive_session,
792        Format.HTML: scapy.autorun_get_html_interactive_session,
793        Format.LATEX: scapy.autorun_get_latex_interactive_session,
794        Format.XUNIT: scapy.autorun_get_text_interactive_session,
795        }
796
797    if VERB > 2:
798        print("### Starting tests...", file=sys.stderr)
799
800    glob_output = ""
801    glob_result = 0
802    glob_title = None
803
804    UNIQUE = len(TESTFILES) == 1
805
806    # Resolve tags and asterix
807    for prex in six.iterkeys(copy.copy(PREEXEC_DICT)):
808        if "*" in prex:
809            pycode = PREEXEC_DICT[prex]
810            del PREEXEC_DICT[prex]
811            for gl in glob.iglob(prex):
812                _pycode = pycode.replace("%name%", os.path.splitext(os.path.split(gl)[1])[0])
813                PREEXEC_DICT[gl] = _pycode
814
815    pos_begin = 0
816
817    runned_campaigns = []
818    # Execute all files
819    for TESTFILE in TESTFILES:
820        if VERB > 2:
821            print("### Loading:", TESTFILE, file=sys.stderr)
822        PREEXEC = PREEXEC_DICT[TESTFILE] if TESTFILE in PREEXEC_DICT else GLOB_PREEXEC
823        output, result, campaign = execute_campaign(open(TESTFILE), OUTPUTFILE,
824                                          PREEXEC, NUM, KW_OK, KW_KO,
825                                          DUMP, FORMAT, VERB, ONLYFAILED,
826                                          CRC, autorun_func, pos_begin, ignore_globals)
827        runned_campaigns.append(campaign)
828        pos_begin = campaign.end_pos
829        if UNIQUE:
830            glob_title = campaign.title
831        glob_output += output
832        if not result:
833            glob_result = 1
834            break
835
836    if VERB > 2:
837            print("### Writing output...", file=sys.stderr)
838    # Concenate outputs
839    if FORMAT == Format.HTML:
840        glob_output = pack_html_campaigns(runned_campaigns, glob_output, LOCAL, glob_title)
841
842    OUTPUTFILE.write(glob_output.encode("utf8", "ignore")
843                     if 'b' in OUTPUTFILE.mode else glob_output)
844    OUTPUTFILE.close()
845
846    # Return state
847    return glob_result
848
849if __name__ == "__main__":
850    sys.exit(main(sys.argv[1:]))
851