1
2
3 """'epyunit' - Command line interface
4
5 The epyunit commandline interface provides a call wrapper
6 for unit and regression tests of arbitrary executables.
7 This also includes the management of seammless cross-process
8 debugging based on PyDev/Eclipse.
9
10 The wrapper internally relies on the standard packages 'PyUnit'
11 and integrates into Eclipse by 'PyDev'. Thus unit tests could
12 be applied in particular for shell scripts and intermixed
13 application processes implemented in multiple programming
14 languages. Automation of remote debugging by PyDev is
15 supported.
16
17 The call is simply a prefix to the actual testee including
18 it's options. The wrapper itself provides various criteria
19 for the indication of the success and/or failure of the test
20 case. Therefore correlation of stdout, stderr, and exit
21 values is provided.
22
23
24 **SYNOPSIS**:
25
26 epyunit [OPTIONS] [--] <testee> [<testee-options>]
27
28 The following categories of options are provided:
29
30 rulesets:
31
32 --exitign, --exittype, --exitval, --priotype
33 --redebug, --redotall, --reignorecase, --remultiline,
34 --result, --resultnok, --resultok, --reunicode,
35 --stderrnok, --stderrok, --stdoutnok, --stdoutok,
36
37
38 output and format:
39
40 --csv, --pass, --passall, --raw, --repr, --str, --xml
41
42 --appname, --test-id, --timestamp
43
44 process wrapper:
45
46 --cp, --cp-prepend, --cp-append
47
48 --debug, --environment, --help, -Version, --Version,
49 --verbose, -version, --version
50
51 --selftest, --slang, --subproc, --subunit,
52
53 --exit-unit-ok, --exit-unit-failed
54
55 subprocess debugging:
56
57 --pydev-remote-debug, --rdbg, --rdbg-forward
58 --rdbg-env
59
60 **OPTIONS**:
61 .
62
63 --appname=<arbitrary-name-of-app>
64
65 An arbitrary application name to be inserted into record
66 headers.
67
68 --cp=<path-list>
69
70 Classpath for module search, replaces sys.path.
71
72 --cp-prepend=<path-list>
73
74 Classpath for module search, inserted at the beginning of
75 sys.path.
76
77 --cp-append=<path-list>
78
79 Classpath for module search, appended at the end of
80 sys.path.
81
82 --csv
83
84 Prints complete test result CSV format including header.
85
86 -d --debug
87
88 Debug entries, does NOT work with 'python -O ...'.
89 Developer output, aimed for filtering.
90
91 --environment
92
93 Include platform info into header.
94
95 --exitign=(True|False)
96
97 Ignore exit value.
98
99 --exittype=(True|False)
100
101 Exit value 'True' indicates success for '0',
102 'False' indicates success for '!=0'.
103
104 --exit-unit-failed=<exit-value>
105
106 Exit value to be emitted in case of failure of unittest.
107
108 default := 1
109
110 --exit-unit-ok=<exit-value>
111
112 Exit value to be emitted in case of success of unittest.
113
114 default := 0
115
116 --exitval=<exit-value>
117
118 Indicates success when exit value is equal to the provided
119 value.
120
121 -h --help
122
123 This help.
124
125 --pass
126
127 Pass through the testee results on STDOUT and STDERR.
128 The exit value is interpreted by rules, else the
129 execution state of the framework defines the exit
130 value.
131
132 --passall
133
134 Pass through the testee result on STDOUT and STDERR
135 including transparently the received exit value.
136
137 --priotype
138
139 In case of present failure and success conditions,
140
141 TRUE: the success condition dominates.
142
143 FALSE: the failure condition dominates.
144
145 --pydev-remote-debug[=host[:port]]
146
147 Activates remote debugging with PyDev plugin of Eclipse.
148
149 --raw
150
151 Same as '--passall'
152
153 --rdbg[=host[:port]]
154
155 Activate remode debug, optionally the host and port number
156 of the server process could be changed.
157
158 default:=localhost:5678
159
160 --rdbg-env
161
162 Enables the readout of RDBG environment variables:
163 ::
164
165 RDBGROOT, RDBGSUB
166
167 --rdbg-forward=(<forwarding-levels>|all|label)
168
169 Forward the '--rdbg' option to subprocesses for nested debugging
170 of process chains.
171
172 <forwarding-levels>: Number of levels to be forwarded, 0==None.
173
174 all: all nested subprocesses
175
176 label: An arbitrary label defined at initialization of the
177 debug instance. Debugging is enabled when these match.
178
179 default:=0: No forwarding.
180
181 --redebug
182
183 Enables 're.DEBUG'
184
185 --redotall:
186
187 Enables 're.DOTALL'
188
189 --reignorecase:
190
191 Enables 're.IGNORECASE'.
192
193 --remultiline:
194
195 Enables 're.MULTILINE'.
196
197 --repr
198
199 Prints complete test result by Python call of 'repr()'.
200
201 --result=#total-results
202
203 The treshold of the total matched results for changing
204 the overall state to success.
205
206 --resultnok=#total-failure-results
207
208 The treshold of the total matched failure results for
209 changing the overall state to success.
210
211 --resultok=#total-success-results
212
213 The treshold of the total matched success results for
214 changing the overall state to success.
215
216 --reunicode:
217
218 Enables 're.UNICODE'.
219
220 --selftest
221
222 Performs a basic functional selftest by executing the
223 basic examples based on 'myscript.<postfix>', see '--slang'.
224
225 --slang[=(bash|perl|python|<file-path-name>)
226
227 Sets the subprocess script by it's programming language for
228 the '--selftest' resource simulator. The following postfixes
229 are currently available:
230
231 slang=bash -> postfix=sh
232
233 slang=perl -> postfix=pl
234
235 slang=python -> postfix=py
236
237 <file-path-name> -> "calls the provided executable"
238
239 default:=python
240
241 --stderrnok=<nok-string>
242
243 Error string on stderr indicates success.
244
245 --stderrok=<ok-string>
246
247 OK string on stderr indicates success.
248
249 --stdoutnok=<nok-string>
250
251 Error string on stdout indicates success.
252
253 --stdoutok=<ok-string>
254
255 OK string on stdout indicates success.
256
257 --str
258
259 Prints complete test result by Python call of 'str()'.
260
261 --subproc
262
263 Starts the subprocess by: 'epyunit.SystemCalls'
264 This mode also switches the output to '--passall' by
265 default, when another output mode is required, set
266 the required option after the '--subproc' option.
267
268 --subunit
269
270 Starts the subprocess by the default: 'epyunit.SubprocessUnit'
271
272 --test-id=<arbitrary-identifier-for-record-header>
273
274 Prints the test-id with the formats 'csv', and 'xml'.
275 Too be applied in case of multiple test case calls.
276
277 --timestamp
278
279 Includes date and time into record header.
280
281 -Version --Version
282
283 Current version - detailed.
284
285 -v --verbose
286
287 Verbose, some relevant states for basic analysis.
288 When '--selftest' is set, repetition raises the display
289 level.
290
291 -version --version
292
293 Current version - terse.
294
295 --xml
296
297 Prints complete test result XML format.
298
299
300 **ARGUMENTS**:
301
302 [--]
303
304 The double hyphen terminates the options of the call,
305 thus the remaining part of the call is treated as the
306 subcall of the testee.
307
308 <testee>
309
310 The wrapped testee.
311
312 [<testee-options>]
313
314 Options of the testee.
315
316
317 **ENVIRONMENT**:
318
319 * PYTHON OPTIONS:
320
321 -O, -OO: Eliminates '__debug__' code.
322
323
324 **EXAMPLES**:
325
326 Basic call examples are provided:
327
328 * `CLI: command line interface <epyunit_example_cli.html>`_
329
330 * `Eclipse: PyDev integration <epyunit_example_eclipse.html>`_
331
332 For detailed examples refer to the subdirectories of the
333 source package for:
334
335 * Unit tests
336
337 * UseCases
338
339 **SEE ALSO**:
340
341 * https://pypi.python.org/pypi/epyunit/
342
343 * https://pythonhosted.org/epyunit/
344
345 **COPYRIGHT**:
346 Arno-Can Uestuensoez @Ingenieurbuero Arno-Can Uestuensoez
347 Copyright (C)2015-2016 Arno-Can Uestuensoez
348
349 """
350 from __future__ import absolute_import
351
352
353 __author__ = 'Arno-Can Uestuensoez'
354 __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
355 __copyright__ = "Copyright (C) 2010-2016 Arno-Can Uestuensoez @Ingenieurbuero Arno-Can Uestuensoez"
356 __version__ = '0.2.0'
357 __uuid__='9de52399-7752-4633-9fdc-66c87a9200b8'
358 __release__ = 'alpha2'
359 __docformat__ = "restructuredtext en"
360
361 import os, sys, platform, getopt
362
363
364
365
366
367
368 if '--appname' in sys.argv:
369 _ai = sys.argv.index('--appname')
370 _APPNAME = sys.argv[_ai]
371 else:
372 _APPNAME = "epyunit"
373
374
375 _host = platform.node()
376 _user = "testuser"
377 _osu = platform.uname()
378 _os = _osu[0]
379 _osver = _osu[2]
380 _arch = _osu[-1]
381 _dist, _distver,_x = platform.dist()
382
383
384
385 try:
386 from epyunit.SystemCalls import SystemCalls
387 from epyunit.SubprocUnit import SubprocessUnit
388 except Exception as e:
389 print "\n#\n#*** Set 'PYTHONPATH' ("+str(e)+")\n#\n"
390 sys.exit(1)
391
394
396 if __name__ == '__main__':
397 import pydoc
398
399 print pydoc.help(__name__)
400 else:
401 help(str(os.path.basename(sys.argv[0]).split('.')[0]))
402
403 _longopts = [
404
405
406 "priotype=", "result=", "resultnok=", "resultok=",
407
408
409 "exitval=","exitign=","exittype=",
410
411
412 "stderrnok=","stdoutnok=","stderrok=","stdoutok=",
413 "redebug","redotall","reignorecase","remultiline",
414 "reunicode",
415
416
417
418 "repr", "xml", "csv", "str", "pass", "passall", "raw",
419
420
421 "appname=", "test-id=", "timestamp", "environment",
422 "subproc", "subunit",
423
424 "exit-unit-failed", "exit-unit-ok",
425
426
427 "help","debug","verbose","version","Version",
428 "selftest", "slang=",
429
430
431
432 "default-nok", "default-ok",
433 ]
434 _sopts = "a:hdv"
435
437 print "\nAvailable options:"
438 slst = ""
439 nl = 0
440 print "\nshortopts:"
441 for s in _sopts:
442 if nl == 10:
443 print " "+slst
444 nl = -1
445 slst = ""
446 if s == ':':
447 continue
448 slst += "-%s "%(s)
449 if slst:
450 print " "+slst
451
452 ilst = ""
453 nl = 0
454 print "\nlongopts:"
455 for i in sorted(_longopts):
456 if nl == 2 or nl>=len(_longopts):
457 print " "+ilst
458 nl = -1
459 ilst = ""
460 ilst += "--%-20s "%(i)
461 nl += 1
462 if ilst:
463 print " "+ilst
464 print """
465 Examples:
466
467 epyu -v --selftest
468 epyu -v -v --selftest
469 epyu -v -v -v --selftest
470 epyu -v --selftest --slang=bash # use bash: myscript.sh
471 epyu -v --selftest --slang=python # use perl: myscript.pl
472 epyu -v --selftest --slang=perl # use python: myscript.py
473 epyu -v myscript.py NOK
474 epyu -v --exit=8 myscript.py EXIT8
475 epyu -v --exit=8 myscript.pl EXIT8
476 epyu -v --exit=8 myscript.py EXIT8
477
478
479 Reminder:
480
481 * set PYTHONPATH
482 * set PATH for 'epyu' to 'bin' directory
483 * on Windows prefer to use 'epyu.py' with PATHEXT
484 * set PATH for 'myscript.EXT'<EXT:=(sh|py|pl)> to 'epyunit' directory
485 * use 'myscript.EXT -h'<EXT:=(sh|py|pl)> for all response pattern
486 * use '--help' for complete help
487 * start Eclipse and RemoteDebugServer of PyDev for cross-process debugging
488 * consider using '--'(double-hyphen), which makes nested commands handy
489 partially required mandatory
490
491 """
492
493
494
495
496 if "--help" in sys.argv or "-help" in sys.argv:
497 usage()
498 sys.exit()
499
500 if "-h" in sys.argv:
501 usagemin()
502 sys.exit()
503
504
505
506
507
508
509
510
511 _tmp = sys.path.pop(0)
512 import epyunit.debug.checkRDbg
513 _rdbgthis,_rdbg,_rdbgfwd,_rdbgroot,_rdbgsub = epyunit.debug.checkRDbg.checkAndRemoveRDbgOptions(**{'label':_APPNAME,})
514 """
515 pydev remote debug options:
516 _rdbgthis: requested debugging status for this process instance
517 _rdbg: the remote debugging peer for this instance
518 _rdbgfwd: requested state forwarding to nested subprocess levels
519 _rdbgroot: rootdirectory of eclipse
520 _rdbgsub: sub directory of PyDev for 'pydevd.py'
521 """
522
523 sys.path.insert(0,_tmp)
524 if _rdbgthis:
525
526 import epyunit.debug.pydevrdc
527 epyunit.debug.pydevrdc.PYDEVD.startDebug()
528
529
530
531 pass
532
533 _kargs={}
534 try:
535 _opts, _args = getopt.getopt(sys.argv[1:], _sopts, _longopts)
536 except getopt.GetoptError, err:
537 print str(err)
538 usagemin()
539 sys.exit(2)
540
541
542
543
544
545
546
547 _appname = None
548
549
550 _testid = 0
551
552
553 _selftest = False
554 _slang = 'python'
555
556
557 _verbose = 0
558
559
560 _debug = 0
561
562
563 _default = 'OK'
564
565
566 _prio = "NOK"
567
568
569 _exit = "OK"
570 _exitokval = 0
571 _exitnok = "NOK"
572 _exitnokval = 1
573
574
575
576 _chk_exit = True
577
578
579 _chk_stderr = False
580 _CHK_STDERR_OK = []
581 _CHK_STDERR_NOK = []
582
583
584 _chk_stdout = False
585 _CHK_STDOUT_OK = []
586 _CHK_STDOUT_NOK = []
587
588
589 _result = 0
590
591
592 _out = None
593 _timestamp = False
594 _environment = False
595
596
597
598 _myRulesMap = {}
599
600 _O_REPR = 0
601 _O_XML = 1
602 _O_CSV = 2
603 _O_PASS = 3
604 _O_PASSA = 4
605
606
607
608 _CALL_SUBPROC = SubprocessUnit
609
610 for _o,_a in _opts:
611
612
613
614
615
616
617
618
619
620 if _o in ("--priotype",):
621 if _a.lower() in ('true','1','ok',):
622 _myRulesMap['priotype'] = True
623 else:
624 _myRulesMap['priotype'] = False
625
626
627
628
629 elif _o in ("--result",):
630 if type(_a) is int:
631 _myRulesMap['result'] = _a
632 else:
633 raise EPyUnitException("Integer required:"+str(_a))
634
635 elif _o in ("--resultnok",):
636 if type(_a) is int:
637 _myRulesMap['resultnok'] = _a
638 else:
639 raise EPyUnitException("Integer required:"+str(_a))
640 elif _o in ("--resultok",):
641 if type(_a) is int:
642 _myRulesMap['resultok'] = _a
643 else:
644 raise EPyUnitException("Integer required:"+str(_a))
645
646
647
648
649 elif _o in ("--exitval",):
650 _myRulesMap['exitval'] = int(_a)
651 elif _o in ("--exitign",):
652 if _a.lower() in ('true','1','ok',):
653 _myRulesMap['exitign'] = True
654 else:
655 _myRulesMap['exitign'] = False
656 elif _o in ("--exittype",):
657 if _a.lower() in ('true','1','ok',):
658 _myRulesMap['exittype'] = True
659 else:
660 _myRulesMap['exittype'] = False
661
662
663
664
665
666 elif _o in ("--stderrnok",):
667 _CHK_STDERR_NOK.append(_a)
668 elif _o in ("--stderrok",):
669 _CHK_STDERR_OK.append(_a)
670
671
672
673
674 elif _o in ("--stdoutnok",):
675 _CHK_STDOUT_NOK.append(_a)
676 elif _o in ("--stdoutok",):
677 _CHK_STDOUT_OK.append(_a)
678
679
680
681
682
683
684
685
686
687 elif _o in ("--redebug",):
688 _myRulesMap['redebug'] = True
689 elif _o in ("--redotall",):
690 _myRulesMap['dotall'] = True
691 elif _o in ("--reignorecase",):
692 _myRulesMap['ignorecase'] = True
693 elif _o in ("--remultiline",):
694 _myRulesMap['multiline'] = True
695 elif _o in ("--reunicode",):
696 _myRulesMap['unicode'] = True
697
698
699
700
701
702 elif _o in ("--str",):
703 _kargs['out'] = 'str'
704 _out = _O_REPR
705 elif _o in ("--repr",):
706 _kargs['out'] = 'repr'
707 _out = _O_REPR
708 elif _o in ("--xml",):
709 _kargs['out'] = 'xml'
710 _out = _O_XML
711 elif _o in ("--csv",):
712 _kargs['out'] = 'csv'
713 _out = _O_CSV
714 elif _o in ("--pass",):
715 _kargs['out'] = 'pass'
716 _out = _O_PASS
717 elif _o in ("--passall",):
718 _kargs['out'] = 'pass'
719 _out = _O_PASSA
720 elif _o in ("--raw",):
721 _kargs['raw'] = True
722 _kargs['out'] = 'pass'
723 _out = _O_PASSA
724
725
726
727
728
729
730
731
732 elif _o in ("-a","--appname",):
733 _appname = _a
734 elif _o in ("--test-id",):
735 _testid = _a
736 elif _o in ("--timestamp",):
737 _timestamp = True
738 elif _o in ("--environment",):
739 _environment = True
740
741 elif _o == "--selftest":
742 _selftest = True
743
744 elif _o == "--slang":
745 _slang = _a
746
747 elif _o == "--subproc":
748 _CALL_SUBPROC = SystemCalls
749 _kargs['out'] = 'pass'
750 _out = _O_PASSA
751
752 elif _o == "--subunit":
753 _CALL_SUBPROC = SubprocessUnit
754
755 elif _o == "--exit-unit-failed":
756 _exitokval = _a
757
758 elif _o == "--exit-unit-ok":
759 _exitnokval = _a
760
761
762 elif _o == "--cp":
763 sys.path = _a
764 elif _o == "--cp-prepend":
765 sys.path.insert(0,_a+os.pathsep)
766 elif _o == "--cp-append":
767 sys.path.append(os.pathsep+_a)
768
769
770
771
772 elif _o in ("-d","--debug",):
773 _kargs['debug'] = True
774 _debug += 1
775 elif _o in ("-v","--verbose",):
776 _verbose += 1
777
778
779 elif _o in ("--version",):
780 print str(__version__)
781 sys.exit()
782
783 elif _o in ("--Version",):
784 print "app: "+str(_APPNAME)
785 print "version: "+str(__version__)
786 print "author: "+str(__author__)
787 print "copyright:"+str(__copyright__)
788 print "license: "+str(__license__)
789 print "file: "+str(os.path.basename(__file__))
790 sys.exit()
791
792 else:
793 assert False, "unhandled option"+str(_o)
794
795
796
797
798
799 if _CHK_STDERR_NOK:
800 _myRulesMap["stderrnok"] = _CHK_STDERR_NOK
801 if _CHK_STDERR_OK:
802 _myRulesMap["stderrok"] = _CHK_STDERR_OK
803 if _CHK_STDOUT_NOK:
804 _myRulesMap["stdoutnok"] = _CHK_STDOUT_NOK
805 if _CHK_STDOUT_OK:
806 _myRulesMap["stdoutok"] = _CHK_STDOUT_OK
807
808
809 if _selftest:
810 import epyunit.selftest
811
812 _myargs = {}
813 if _out == _O_REPR:
814 _myargs['out'] = 'repr'
815 elif _out == _O_XML:
816 _myargs['out'] = 'xml'
817 elif _out == _O_PASS:
818 _myargs['out'] = 'pass'
819 elif _out == _O_PASSA:
820 _myargs['out'] = 'pass-all'
821 elif _out == _O_CSV:
822 _myargs['out'] = 'csv'
823 elif _verbose and not _out:
824 _myargs['out'] = 'str'
825
826 if _verbose:
827 _myargs['verbose'] = _verbose
828 if _debug:
829 _myargs['debug'] = _debug
830
831 if _rdbgfwd:
832 _myargs['rdbgforward'] = _rdbgfwd
833 _myargs['rdbg'] = _rdbg
834
835 if not _slang:
836 if sys.platform == 'Windows':
837 epyunit.selftest.selftest('python',**_myargs)
838 else:
839 epyunit.selftest.selftest('python',**_myargs)
840 else:
841 epyunit.selftest.selftest(_slang,**_myargs)
842
843 sys.exit(0)
844
845
846
847
848
849 if _rdbgfwd:
850 _mi = 0
851 for x in _args:
852 if x.startswith('-'):
853 break
854 _mi += 1
855 if _mi == 0:
856 _args.append('--rdbg')
857 _args.append('--rdbgforward='+str(_rdbgfwd))
858 else:
859 _args.insert(_mi, '--rdbgforward='+str(_rdbgfwd))
860 _args.insert(_mi, '--rdbg')
861
862
863
864
865 if _verbose>0:
866 _kargs['verbose'] = _verbose
867 if _debug > 0:
868 _kargs['debug'] = _debug
869
870 if _out in (_O_PASS,_O_PASSA,):
871 _kargs['raw'] = True
872
873 _kargs.update(_myRulesMap)
874 sx = _CALL_SUBPROC(**_kargs)
875 if _debug > 0:
876 print str(sx)+"\n"
877 ret = sx.callit(' '.join(_args))
878 if ret[0] == 126:
879 print >>sys.stderr ,"check exec permissions of:"+str(' '.join(_args))
880 if _verbose+_debug > 0:
881 print ret
882
883
884 _unit_status = sx.apply(ret)
885
886
887 sx.displayit(ret)
888
889
890 if _out in (_O_PASSA,):
891 sys.exit(ret[0])
892
893 if _verbose or _debug:
894 print "epyunit => "+str(_result)
895
896 if _unit_status:
897 sys.exit(_exitokval)
898 else:
899 sys.exit(_exitnokval)
900
901
902