From d77af43a417184ef00337ac86705bbba7e61f08f Mon Sep 17 00:00:00 2001 From: William Deegan Date: Wed, 17 Jan 2018 23:35:26 -0500 Subject: add another packaging build dir to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4050468..1807b29 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ __pycache__/ build/** bootstrap/** .idea/ +src/build/** # Translations -- cgit v0.12 From e25d4155b872845af6df188efdf50a660f235572 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Wed, 17 Jan 2018 23:37:06 -0500 Subject: Added find_program_path() to SCons.Tool. Logic was in mingw. Use this to allow tools to search for normal SCOns paths, and then specify alternative default install paths for such tools. This should resolve clang failing on appveyor on windows --- src/engine/SCons/Tool/__init__.py | 29 +++++++++++++++++++++++++++++ src/engine/SCons/Tool/clang.py | 7 +++++++ src/engine/SCons/Tool/clangxx.py | 11 ++++++++++- src/engine/SCons/Tool/mingw.py | 31 +++++++------------------------ 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py index 42f84e1..0b340c0 100644 --- a/src/engine/SCons/Tool/__init__.py +++ b/src/engine/SCons/Tool/__init__.py @@ -1247,6 +1247,35 @@ def tool_list(platform, env): return [x for x in tools if x] + +def find_program_path(env, key_program, default_paths=[]): + """ + Find the location of key_program and then return the path it was located at. + Checking the default install locations. + Mainly for windows where tools aren't all installed in /usr/bin,etc + :param env: Current Environment() + :param key_program: Program we're using to locate the directory to add to PATH. + """ + # First search in the SCons path + path=env.WhereIs(key_program) + if (path): + return path + # then the OS path: + path=SCons.Util.WhereIs(key_program) + if (path): + return path + + # If that doesn't work try default location for mingw + save_path = env['ENV']['PATH'] + for p in default_paths: + env.AppendENVPath('PATH',p) + path = env.WhereIs(key_program) + if not path: + env['ENV']['PATH']=save_path + return path + + + # Local Variables: # tab-width:4 # indent-tabs-mode:nil diff --git a/src/engine/SCons/Tool/clang.py b/src/engine/SCons/Tool/clang.py index 177e6b2..a6bae43 100644 --- a/src/engine/SCons/Tool/clang.py +++ b/src/engine/SCons/Tool/clang.py @@ -52,6 +52,13 @@ def generate(env): """Add Builders and construction variables for clang to an Environment.""" SCons.Tool.cc.generate(env) + if env['PLATFORM'] == 'win32': + # Ensure that we have a proper path for clang + clang = SCons.Tool.find_program_path(env, compilers[0], default_paths=[r'C:\Program Files\LLVM\bin',]) + if clang: + clang_bin_dir = os.path.dirname(clang) + env.AppendENVPath('PATH', clang_bin_dir) + env['CC'] = env.Detect(compilers) or 'clang' if env['PLATFORM'] in ['cygwin', 'win32']: env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') diff --git a/src/engine/SCons/Tool/clangxx.py b/src/engine/SCons/Tool/clangxx.py index dd501af..63f1b0d 100644 --- a/src/engine/SCons/Tool/clangxx.py +++ b/src/engine/SCons/Tool/clangxx.py @@ -66,13 +66,22 @@ def generate(env): env['SHOBJSUFFIX'] = '.pic.o' elif env['PLATFORM'] == 'sunos': env['SHOBJSUFFIX'] = '.pic.o' + elif env['PLATFORM'] == 'win32': + # Ensure that we have a proper path for clang++ + clangxx = SCons.Tool.find_program_path(env, compilers[0], default_paths=[r'C:\Program Files\LLVM\bin',]) + if clangxx: + clangxx_bin_dir = os.path.dirname(clangxx) + env.AppendENVPath('PATH', clangxx_bin_dir) + # determine compiler version if env['CXX']: pipe = SCons.Action._subproc(env, [env['CXX'], '--version'], stdin='devnull', stderr='devnull', stdout=subprocess.PIPE) - if pipe.wait() != 0: return + if pipe.wait() != 0: + return + # clang -dumpversion is of no use line = pipe.stdout.readline() if sys.version_info[0] > 2: diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py index 778db3c..4121902 100644 --- a/src/engine/SCons/Tool/mingw.py +++ b/src/engine/SCons/Tool/mingw.py @@ -42,26 +42,6 @@ import SCons.Defaults import SCons.Tool import SCons.Util -# This is what we search for to find mingw: -key_program = 'mingw32-gcc' - -def find(env): - # First search in the SCons path - path=env.WhereIs(key_program) - if (path): - return path - # then the OS path: - path=SCons.Util.WhereIs(key_program) - if (path): - return path - - # If that doesn't work try default location for mingw - save_path=env['ENV']['PATH'] - env.AppendENVPath('PATH',r'c:\MinGW\bin') - path =env.WhereIs(key_program) - if not path: - env['ENV']['PATH']=save_path - return path def shlib_generator(target, source, env, for_signature): cmd = SCons.Util.CLVar(['$SHLINK', '$SHLINKFLAGS']) @@ -126,12 +106,15 @@ res_builder = SCons.Builder.Builder(action=res_action, suffix='.o', source_scanner=SCons.Tool.SourceFileScanner) SCons.Tool.SourceFileScanner.add_scanner('.rc', SCons.Defaults.CScan) +# This is what we search for to find mingw: +key_program = 'mingw32-gcc' + + def generate(env): - mingw = find(env) + mingw = SCons.Tool.find_program_path(env, key_program, default_paths=[r'c:\MinGW\bin',]) if mingw: - dir = os.path.dirname(mingw) - env.PrependENVPath('PATH', dir ) - + mingw_bin_dir = os.path.dirname(mingw) + env.AppendENVPath('PATH', mingw_bin_dir) # Most of mingw is the same as gcc and friends... gnu_tools = ['gcc', 'g++', 'gnulink', 'ar', 'gas', 'gfortran', 'm4'] -- cgit v0.12 From a4ca5012e89b136ffbc0d7fb201976551bf63227 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Wed, 17 Jan 2018 23:42:48 -0500 Subject: Add info on changes --- src/CHANGES.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 42b44d7..df25dac 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -22,6 +22,13 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - Updated Jar builder to flatten source list which could contain embedded lists - Removed some magic numbers from jar.py on behalf of Mats Wichmann (mats@linux.com) + From William Deegan: + - Updated logic for mingw and clang on win32 to search default tool install paths if not + found in normal SCons PATH. If the user specifies PATH or tool specific paths they + will be used and the default paths below will be ignored. + - Default path for clang/clangxx : C:\Program Files\LLVM\bin + - Default path for mingw : c:\MinGW\bin + RELEASE 3.0.1 - Mon, 12 Nov 2017 15:31:33 -0700 From Daniel Moody: -- cgit v0.12 From 4685be01723dd4ef4917217e8aff25e92a55f1a5 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Wed, 14 Feb 2018 11:44:59 -0500 Subject: Update default paths for clang compilers. This should hopefully fix some appveyor failures. --- src/engine/SCons/Tool/clang.py | 5 ++++- src/engine/SCons/Tool/clang/__init__.py | 16 ++++++++++++++++ src/engine/SCons/Tool/clangxx.py | 4 +++- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/engine/SCons/Tool/clang/__init__.py diff --git a/src/engine/SCons/Tool/clang.py b/src/engine/SCons/Tool/clang.py index a6bae43..aba5598 100644 --- a/src/engine/SCons/Tool/clang.py +++ b/src/engine/SCons/Tool/clang.py @@ -45,6 +45,8 @@ import sys import SCons.Util import SCons.Tool.cc +from SCons.Tool.clang import get_clang_install_dirs + compilers = ['clang'] @@ -54,7 +56,7 @@ def generate(env): if env['PLATFORM'] == 'win32': # Ensure that we have a proper path for clang - clang = SCons.Tool.find_program_path(env, compilers[0], default_paths=[r'C:\Program Files\LLVM\bin',]) + clang = SCons.Tool.find_program_path(env, compilers[0], default_paths=get_clang_install_dirs(env['PLATFORM'])) if clang: clang_bin_dir = os.path.dirname(clang) env.AppendENVPath('PATH', clang_bin_dir) @@ -64,6 +66,7 @@ def generate(env): env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') else: env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC') + # determine compiler version if env['CC']: #pipe = SCons.Action._subproc(env, [env['CC'], '-dumpversion'], diff --git a/src/engine/SCons/Tool/clang/__init__.py b/src/engine/SCons/Tool/clang/__init__.py new file mode 100644 index 0000000..775ba80 --- /dev/null +++ b/src/engine/SCons/Tool/clang/__init__.py @@ -0,0 +1,16 @@ +""" +Common routines and data for clang tools +""" + +clang_win32_dirs = [ + r'C:\Program Files\LLVM\bin', + r'C:\cygwin64\bin', + r'C:\msys64', + r'C:\cygwin\bin', + r'C:\msys', +] + +def get_clang_install_dirs(platform): + if platform == 'win32': + return clang_win32_dirs + else return [] \ No newline at end of file diff --git a/src/engine/SCons/Tool/clangxx.py b/src/engine/SCons/Tool/clangxx.py index 63f1b0d..2bf4cf4 100644 --- a/src/engine/SCons/Tool/clangxx.py +++ b/src/engine/SCons/Tool/clangxx.py @@ -46,6 +46,8 @@ import sys import SCons.Tool import SCons.Util import SCons.Tool.cxx +from SCons.Tool.clang import get_clang_install_dirs + compilers = ['clang++'] @@ -68,7 +70,7 @@ def generate(env): env['SHOBJSUFFIX'] = '.pic.o' elif env['PLATFORM'] == 'win32': # Ensure that we have a proper path for clang++ - clangxx = SCons.Tool.find_program_path(env, compilers[0], default_paths=[r'C:\Program Files\LLVM\bin',]) + clangxx = SCons.Tool.find_program_path(env, compilers[0], default_paths=get_clang_install_dirs(env['PLATFORM'])) if clangxx: clangxx_bin_dir = os.path.dirname(clangxx) env.AppendENVPath('PATH', clangxx_bin_dir) -- cgit v0.12 From 0458fbeaddae9f4bb1e977ccd642a201c7b46c5f Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sat, 24 Mar 2018 12:42:09 -0700 Subject: fix #3106 MSVC handling MSVC_BATCH with targetdir which requires escaping. --- src/CHANGES.txt | 2 ++ src/engine/SCons/Tool/msvc.py | 5 ++++- test/MSVC/MSVC_BATCH-spaces-targetdir.py | 11 +++++++++++ test/MSVC/MSVC_BATCH-spaces-targetdir/SConstruct | 8 ++++++++ test/MSVC/MSVC_BATCH-spaces-targetdir/src/a.c | 15 +++++++++++++++ test/MSVC/MSVC_BATCH-spaces-targetdir/src/b.c | 5 +++++ test/MSVC/MSVC_BATCH-spaces-targetdir/src/c.c | 5 +++++ test/MSVC/batch.py | 7 ++++--- 8 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 test/MSVC/MSVC_BATCH-spaces-targetdir.py create mode 100644 test/MSVC/MSVC_BATCH-spaces-targetdir/SConstruct create mode 100644 test/MSVC/MSVC_BATCH-spaces-targetdir/src/a.c create mode 100644 test/MSVC/MSVC_BATCH-spaces-targetdir/src/b.c create mode 100644 test/MSVC/MSVC_BATCH-spaces-targetdir/src/c.c diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 954af11..1cdc943 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -25,6 +25,8 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From William Deegan: - Remove long deprecated SCons.Options code and tests. This removes BoolOption,EnumOption, ListOption,PackageOption, and PathOption which have been replaced by *Variable() many years ago. + - Fix issue # 3106 MSVC if using MSVC_BATCH and target dir had a space would fail due to quirk in + MSVC's handling of escaped targetdirs when batch compiling. From Andrew Featherstone - Removed unused --warn options from the man page and source code. diff --git a/src/engine/SCons/Tool/msvc.py b/src/engine/SCons/Tool/msvc.py index e1b0536..f078c62 100644 --- a/src/engine/SCons/Tool/msvc.py +++ b/src/engine/SCons/Tool/msvc.py @@ -195,7 +195,10 @@ def msvc_output_flag(target, source, env, for_signature): # that the test(s) for this can be run on non-Windows systems # without having a hard-coded backslash mess up command-line # argument parsing. - return '/Fo${TARGET.dir}' + os.sep + # Adding double os.sep's as if the TARGET.dir has a space or otherwise + # needs to be quoted they are needed per MSVC's odd behavior + # See: https://github.com/SCons/scons/issues/3106 + return '/Fo${TARGET.dir}' + os.sep*2 CAction = SCons.Action.Action("$CCCOM", "$CCCOMSTR", batch_key=msvc_batch_key, diff --git a/test/MSVC/MSVC_BATCH-spaces-targetdir.py b/test/MSVC/MSVC_BATCH-spaces-targetdir.py new file mode 100644 index 0000000..298e10e --- /dev/null +++ b/test/MSVC/MSVC_BATCH-spaces-targetdir.py @@ -0,0 +1,11 @@ +import TestSCons + + + +test = TestSCons.TestSCons() + +test.skip_if_not_msvc() + + +test.dir_fixture('MSVC_BATCH-spaces-targetdir') +test.run() \ No newline at end of file diff --git a/test/MSVC/MSVC_BATCH-spaces-targetdir/SConstruct b/test/MSVC/MSVC_BATCH-spaces-targetdir/SConstruct new file mode 100644 index 0000000..da8002b --- /dev/null +++ b/test/MSVC/MSVC_BATCH-spaces-targetdir/SConstruct @@ -0,0 +1,8 @@ +import os.path + +env=Environment(MSVC_BATCH=True) + +td='tar ge tdir' +VariantDir(td,'src') +env.Program(os.path.join(td,'test_program'), + [os.path.join(td,a) for a in ['a.c','b.c','c.c']]) diff --git a/test/MSVC/MSVC_BATCH-spaces-targetdir/src/a.c b/test/MSVC/MSVC_BATCH-spaces-targetdir/src/a.c new file mode 100644 index 0000000..1741de8 --- /dev/null +++ b/test/MSVC/MSVC_BATCH-spaces-targetdir/src/a.c @@ -0,0 +1,15 @@ +#include + +extern void myfuncb(); +extern void myfuncc(); + + +void myfunca() { + printf("myfunca\n"); +} + +int main(int argc, char *argv[]) { + myfunca(); + myfuncb(); + myfuncc(); +} \ No newline at end of file diff --git a/test/MSVC/MSVC_BATCH-spaces-targetdir/src/b.c b/test/MSVC/MSVC_BATCH-spaces-targetdir/src/b.c new file mode 100644 index 0000000..e03c5d0 --- /dev/null +++ b/test/MSVC/MSVC_BATCH-spaces-targetdir/src/b.c @@ -0,0 +1,5 @@ +#include + +void myfuncb() { + printf("myfuncb\n"); +} \ No newline at end of file diff --git a/test/MSVC/MSVC_BATCH-spaces-targetdir/src/c.c b/test/MSVC/MSVC_BATCH-spaces-targetdir/src/c.c new file mode 100644 index 0000000..1c262d3 --- /dev/null +++ b/test/MSVC/MSVC_BATCH-spaces-targetdir/src/c.c @@ -0,0 +1,5 @@ +#include + +void myfuncc() { + printf("myfuncc\n"); +} \ No newline at end of file diff --git a/test/MSVC/batch.py b/test/MSVC/batch.py index 8b7945b..af5b43e 100644 --- a/test/MSVC/batch.py +++ b/test/MSVC/batch.py @@ -72,6 +72,7 @@ for infile in sys.argv[2:]: """) test.write('SConstruct', """ +DefaultEnvironment(tools=[]) cccom = r'%(_python_)s fake_cl.py $_MSVC_OUTPUT_FLAG $CHANGED_SOURCES' linkcom = r'%(_python_)s fake_link.py ${TARGET.windows} $SOURCES' env = Environment(tools=['msvc', 'mslink'], @@ -96,7 +97,7 @@ test.run(arguments = 'MSVC_BATCH=1 .') test.must_match('prog.exe', "prog.c\nf1.c\nf2.c\n", mode='r') test.must_match('fake_cl.log', """\ -/Fo. prog.c f1.c f2.c +/Fo.\\ prog.c f1.c f2.c """, mode='r') test.up_to_date(options = 'MSVC_BATCH=1', arguments = '.') @@ -109,8 +110,8 @@ test.run(arguments = 'MSVC_BATCH=1 .') test.must_match('prog.exe', "prog.c\nf1.c 2\nf2.c\n", mode='r') test.must_match('fake_cl.log', """\ -/Fo. prog.c f1.c f2.c -/Fo. f1.c +/Fo.\\ prog.c f1.c f2.c +/Fo.\\ f1.c """, mode='r') test.up_to_date(options = 'MSVC_BATCH=1', arguments = '.') -- cgit v0.12 From b42fd7c0e76ca252ce778e5258be1e770561704d Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sat, 24 Mar 2018 17:23:50 -0400 Subject: Fix test so use os.sep instead of hardcoded backslash for MSVC_BATCH=true /Fo argument --- test/MSVC/batch.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/MSVC/batch.py b/test/MSVC/batch.py index af5b43e..0d3063f 100644 --- a/test/MSVC/batch.py +++ b/test/MSVC/batch.py @@ -32,6 +32,7 @@ explicit suffix settings so that the test should work when run on any platform. """ +import os import TestSCons test = TestSCons.TestSCons() @@ -97,8 +98,8 @@ test.run(arguments = 'MSVC_BATCH=1 .') test.must_match('prog.exe', "prog.c\nf1.c\nf2.c\n", mode='r') test.must_match('fake_cl.log', """\ -/Fo.\\ prog.c f1.c f2.c -""", mode='r') +/Fo.%s prog.c f1.c f2.c +"""%os.sep, mode='r') test.up_to_date(options = 'MSVC_BATCH=1', arguments = '.') @@ -110,9 +111,9 @@ test.run(arguments = 'MSVC_BATCH=1 .') test.must_match('prog.exe', "prog.c\nf1.c 2\nf2.c\n", mode='r') test.must_match('fake_cl.log', """\ -/Fo.\\ prog.c f1.c f2.c -/Fo.\\ f1.c -""", mode='r') +/Fo.%s prog.c f1.c f2.c +/Fo.%s f1.c +"""%(os.sep, os.sep), mode='r') test.up_to_date(options = 'MSVC_BATCH=1', arguments = '.') -- cgit v0.12 From 0cefc2bf199dd56d16b752042f9569a420b6ddab Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 25 Mar 2018 12:53:56 -0400 Subject: rename clang command package to clangCommon as it was breaking imports test. --- src/engine/SCons/Tool/clang.py | 2 +- src/engine/SCons/Tool/clang/__init__.py | 16 ---------------- src/engine/SCons/Tool/clangCommon/__init__.py | 17 +++++++++++++++++ src/engine/SCons/Tool/clangxx.py | 2 +- 4 files changed, 19 insertions(+), 18 deletions(-) delete mode 100644 src/engine/SCons/Tool/clang/__init__.py create mode 100644 src/engine/SCons/Tool/clangCommon/__init__.py diff --git a/src/engine/SCons/Tool/clang.py b/src/engine/SCons/Tool/clang.py index aba5598..c51e753 100644 --- a/src/engine/SCons/Tool/clang.py +++ b/src/engine/SCons/Tool/clang.py @@ -45,7 +45,7 @@ import sys import SCons.Util import SCons.Tool.cc -from SCons.Tool.clang import get_clang_install_dirs +from SCons.Tool.clangCommon import get_clang_install_dirs compilers = ['clang'] diff --git a/src/engine/SCons/Tool/clang/__init__.py b/src/engine/SCons/Tool/clang/__init__.py deleted file mode 100644 index 775ba80..0000000 --- a/src/engine/SCons/Tool/clang/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Common routines and data for clang tools -""" - -clang_win32_dirs = [ - r'C:\Program Files\LLVM\bin', - r'C:\cygwin64\bin', - r'C:\msys64', - r'C:\cygwin\bin', - r'C:\msys', -] - -def get_clang_install_dirs(platform): - if platform == 'win32': - return clang_win32_dirs - else return [] \ No newline at end of file diff --git a/src/engine/SCons/Tool/clangCommon/__init__.py b/src/engine/SCons/Tool/clangCommon/__init__.py new file mode 100644 index 0000000..37efbf6 --- /dev/null +++ b/src/engine/SCons/Tool/clangCommon/__init__.py @@ -0,0 +1,17 @@ +""" +Common routines and data for clang tools +""" + +clang_win32_dirs = [ + r'C:\Program Files\LLVM\bin', + r'C:\cygwin64\bin', + r'C:\msys64', + r'C:\cygwin\bin', + r'C:\msys', +] + +def get_clang_install_dirs(platform): + if platform == 'win32': + return clang_win32_dirs + else: + return [] \ No newline at end of file diff --git a/src/engine/SCons/Tool/clangxx.py b/src/engine/SCons/Tool/clangxx.py index 2bf4cf4..7194d9e 100644 --- a/src/engine/SCons/Tool/clangxx.py +++ b/src/engine/SCons/Tool/clangxx.py @@ -46,7 +46,7 @@ import sys import SCons.Tool import SCons.Util import SCons.Tool.cxx -from SCons.Tool.clang import get_clang_install_dirs +from SCons.Tool.clangCommon import get_clang_install_dirs compilers = ['clang++'] -- cgit v0.12 From 14d20880efb6029420ff7c9fd55da4ee26893098 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 25 Mar 2018 13:41:06 -0400 Subject: Fix clang and mingw tests --- src/engine/SCons/Tool/mingw.py | 7 ++++++- test/import.py | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py index 4121902..de493f0 100644 --- a/src/engine/SCons/Tool/mingw.py +++ b/src/engine/SCons/Tool/mingw.py @@ -156,7 +156,12 @@ def generate(env): env['PROGSUFFIX'] = '.exe' def exists(env): - return find(env) + mingw = SCons.Tool.find_program_path(env, key_program, default_paths=[r'c:\MinGW\bin',]) + if mingw: + mingw_bin_dir = os.path.dirname(mingw) + env.AppendENVPath('PATH', mingw_bin_dir) + + return mingw # Local Variables: # tab-width:4 diff --git a/test/import.py b/test/import.py index b6e5a8d..11fee9c 100644 --- a/test/import.py +++ b/test/import.py @@ -75,6 +75,8 @@ ignore = ('__init__.py', '386asm.py', 'linkloc.py', # Directory of common stuff for MSVC and MSVS 'MSCommon', + # clang common + "clangCommon", # Sun pkgchk and pkginfo common stuff 'sun_pkg.py', # RPM utilities -- cgit v0.12 From 23f357c1addfe7edbccbf0b804b7b29c12b5049c Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 25 Mar 2018 13:51:42 -0400 Subject: added more default mingw paths per comments on pull request --- src/engine/SCons/Tool/mingw.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py index de493f0..f2e9442 100644 --- a/src/engine/SCons/Tool/mingw.py +++ b/src/engine/SCons/Tool/mingw.py @@ -43,6 +43,14 @@ import SCons.Tool import SCons.Util +mingw_paths = [ + r'c:\MinGW\bin', + r'C:\cygwin64\bin', + r'C:\msys64', + r'C:\cygwin\bin', + r'C:\msys', +] + def shlib_generator(target, source, env, for_signature): cmd = SCons.Util.CLVar(['$SHLINK', '$SHLINKFLAGS']) @@ -111,7 +119,7 @@ key_program = 'mingw32-gcc' def generate(env): - mingw = SCons.Tool.find_program_path(env, key_program, default_paths=[r'c:\MinGW\bin',]) + mingw = SCons.Tool.find_program_path(env, key_program, default_paths=mingw_paths) if mingw: mingw_bin_dir = os.path.dirname(mingw) env.AppendENVPath('PATH', mingw_bin_dir) @@ -156,7 +164,7 @@ def generate(env): env['PROGSUFFIX'] = '.exe' def exists(env): - mingw = SCons.Tool.find_program_path(env, key_program, default_paths=[r'c:\MinGW\bin',]) + mingw = SCons.Tool.find_program_path(env, key_program, default_paths=mingw_paths) if mingw: mingw_bin_dir = os.path.dirname(mingw) env.AppendENVPath('PATH', mingw_bin_dir) -- cgit v0.12 From 1724fba161d4123f092ba3f850418d78f58d4bad Mon Sep 17 00:00:00 2001 From: grbd Date: Sun, 1 Apr 2018 15:26:19 +0100 Subject: Also search for SConstruct.py, to make VStudio debugging easier --- src/engine/SCons/Script/Main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index f111d35..6bdd8fa 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -623,7 +623,7 @@ def _SConstruct_exists(dirname='', repositories=[], filelist=None): current directory. """ if not filelist: - filelist = ['SConstruct', 'Sconstruct', 'sconstruct'] + filelist = ['SConstruct', 'Sconstruct', 'sconstruct', 'SConstruct.py'] for file in filelist: sfile = os.path.join(dirname, file) if os.path.isfile(sfile): -- cgit v0.12 From c9384f44e9a47e3e2e028667905889fe9206b579 Mon Sep 17 00:00:00 2001 From: grbd Date: Sun, 1 Apr 2018 15:52:14 +0100 Subject: update to changes file --- src/CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 64c0893..72dc513 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -7,6 +7,10 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE + From Richard West: + - Add SConstruct.py to the search path for the root SConstruct file. + Allows easier debugging within Visual Studio + From Daniel Moody: - Set the pickling protocal back to highest which was causing issues with variant dir tests. This will cause issues if reading sconsigns -- cgit v0.12 From ea55726884e48b2b02b1a094dcc38612dd02ad10 Mon Sep 17 00:00:00 2001 From: grbd Date: Sun, 1 Apr 2018 23:11:23 +0100 Subject: Added test for the use of SConstruct.py --- test/SConstruct.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/SConstruct.py b/test/SConstruct.py index cebdf04..62d383d 100644 --- a/test/SConstruct.py +++ b/test/SConstruct.py @@ -39,6 +39,15 @@ scons: \*\*\* No SConstruct file found. wpath = test.workpath() +test.write('SConstruct.py', """ +import os +print("SConstruct.py "+os.getcwd()) +""") + +test.run(arguments = ".", + stdout = test.wrap_stdout(read_str = 'SConstruct.py %s\n' % wpath, + build_str = "scons: `.' is up to date.\n")) + test.write('sconstruct', """ import os print("sconstruct "+os.getcwd()) -- cgit v0.12 From d5a21f6b86636a84d8664364e87102775bbb7a70 Mon Sep 17 00:00:00 2001 From: grbd Date: Mon, 2 Apr 2018 12:59:03 +0100 Subject: updates to docs for SConstruct.py --- doc/design/native.xml | 2 +- doc/man/scons.xml | 14 +++++++++----- doc/scons.mod | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/design/native.xml b/doc/design/native.xml index cd4edaf..25f0d95 100644 --- a/doc/design/native.xml +++ b/doc/design/native.xml @@ -64,7 +64,7 @@ By default, the &SCons; utility searches for a file named - &SConstruct;, &Sconstruct; or &sconstruct; (in that order) in the + &SConstruct;, &Sconstruct;, &sconstruct; or &SConstruct.py; (in that order) in the current directory, and reads its configuration from the first file found. A command-line option exists to read a different file name. diff --git a/doc/man/scons.xml b/doc/man/scons.xml index 03a3478..57c5191 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -105,8 +105,9 @@ rebuild them. searches for a file named SConstruct, Sconstruct, -or sconstruct +or +SConstruct.py (in that order) in the current directory and reads its configuration from the first file found. An alternate file name may be @@ -635,8 +636,9 @@ yet have any results in the cache. before searching for the SConstruct, Sconstruct, -or sconstruct +or +SConstruct.py file, or doing anything else. Multiple @@ -649,8 +651,9 @@ equivalent to except that it will search for SConstruct, Sconstruct, -or sconstruct +or +SConstruct.py in the specified directory.) @@ -1658,9 +1661,10 @@ scons --tree=all,prune,status target Walks up the directory structure until an SConstruct , -Sconstruct -or +Sconstruct , sconstruct +or +SConstruct.py file is found, and uses that as the top of the directory tree. If no targets are specified on the command line, diff --git a/doc/scons.mod b/doc/scons.mod index 448a212..5fa8e13 100644 --- a/doc/scons.mod +++ b/doc/scons.mod @@ -137,6 +137,7 @@ SConstruct"> Sconstruct"> sconstruct"> +SConstruct.py"> .sconsign"> src"> -- cgit v0.12 From cc52bda4954ae1327e7d98cfb4418c64a55056ec Mon Sep 17 00:00:00 2001 From: grbd Date: Mon, 2 Apr 2018 20:14:24 +0100 Subject: Added sconstruct.py lowercase --- doc/design/native.xml | 2 +- doc/man/scons.xml | 20 ++++++++++++-------- doc/scons.mod | 1 + src/CHANGES.txt | 2 +- src/engine/SCons/Script/Main.py | 2 +- test/SConstruct.py | 9 +++++++++ 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/doc/design/native.xml b/doc/design/native.xml index 25f0d95..1329817 100644 --- a/doc/design/native.xml +++ b/doc/design/native.xml @@ -64,7 +64,7 @@ By default, the &SCons; utility searches for a file named - &SConstruct;, &Sconstruct;, &sconstruct; or &SConstruct.py; (in that order) in the + &SConstruct;, &Sconstruct;, &sconstruct;, &SConstruct.py or &sconstruct.py; (in that order) in the current directory, and reads its configuration from the first file found. A command-line option exists to read a different file name. diff --git a/doc/man/scons.xml b/doc/man/scons.xml index 57c5191..cd6541b 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -105,9 +105,10 @@ rebuild them. searches for a file named SConstruct, Sconstruct, -sconstruct -or +sconstruct, SConstruct.py +or +sconstruct.py (in that order) in the current directory and reads its configuration from the first file found. An alternate file name may be @@ -636,9 +637,10 @@ yet have any results in the cache. before searching for the SConstruct, Sconstruct, -sconstruct -or +sconstruct, SConstruct.py +or +sconstruct.py file, or doing anything else. Multiple @@ -651,9 +653,10 @@ equivalent to except that it will search for SConstruct, Sconstruct, -sconstruct -or +sconstruct, SConstruct.py +or +sconstruct.py in the specified directory.) @@ -1662,9 +1665,10 @@ scons --tree=all,prune,status target Walks up the directory structure until an SConstruct , Sconstruct , -sconstruct -or +sconstruct , SConstruct.py +or +sconstruct.py file is found, and uses that as the top of the directory tree. If no targets are specified on the command line, diff --git a/doc/scons.mod b/doc/scons.mod index 5fa8e13..1d207c9 100644 --- a/doc/scons.mod +++ b/doc/scons.mod @@ -138,6 +138,7 @@ Sconstruct"> sconstruct"> SConstruct.py"> +sconstruct.py"> .sconsign"> src"> diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 72dc513..b7aa9dd 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -8,7 +8,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From Richard West: - - Add SConstruct.py to the search path for the root SConstruct file. + - Add SConstruct.py and sconstruct.py to the search path for the root SConstruct file. Allows easier debugging within Visual Studio From Daniel Moody: diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index 6bdd8fa..18c493c 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -623,7 +623,7 @@ def _SConstruct_exists(dirname='', repositories=[], filelist=None): current directory. """ if not filelist: - filelist = ['SConstruct', 'Sconstruct', 'sconstruct', 'SConstruct.py'] + filelist = ['SConstruct', 'Sconstruct', 'sconstruct', 'SConstruct.py', 'sconstruct.py'] for file in filelist: sfile = os.path.join(dirname, file) if os.path.isfile(sfile): diff --git a/test/SConstruct.py b/test/SConstruct.py index 62d383d..3cf1c02 100644 --- a/test/SConstruct.py +++ b/test/SConstruct.py @@ -39,6 +39,15 @@ scons: \*\*\* No SConstruct file found. wpath = test.workpath() +test.write('sconstruct.py', """ +import os +print("sconstruct.py "+os.getcwd()) +""") + +test.run(arguments = ".", + stdout = test.wrap_stdout(read_str = 'sconstruct.py %s\n' % wpath, + build_str = "scons: `.' is up to date.\n")) + test.write('SConstruct.py', """ import os print("SConstruct.py "+os.getcwd()) -- cgit v0.12 From 5e5cccde062fe18290048ab4adb4b2270fdf6c94 Mon Sep 17 00:00:00 2001 From: Fredrik Medley Date: Thu, 29 Mar 2018 15:40:06 +0200 Subject: Fix EnvironmentError printing bug EnvironmentErrors does not need to have the attributes strerror or errno. This commit makes the code robust for such situation. One example of such EnvironmentError is CacheDir.py: raise SCons.Errors.EnvironmentError(msg) Signed-off-by: Fredrik Medley --- src/CHANGES.txt | 5 +++++ src/engine/SCons/Errors.py | 11 +++++------ src/engine/SCons/ErrorsTests.py | 25 +++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 64c0893..4d6d191 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -48,6 +48,11 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE Specifically, this fixes the test cases use by Configure.CheckCC() which would fail when using -Wstrict-prototypes. + From Fredrik Medley: + - Fix exception when printing of EnviromentError messages. + Specifically, this fixes error reporting of the race condition when + initializing the cache which error previously was hidden. + RELEASE 3.0.1 - Mon, 12 Nov 2017 15:31:33 -0700 diff --git a/src/engine/SCons/Errors.py b/src/engine/SCons/Errors.py index dae20b2..3746d5d 100644 --- a/src/engine/SCons/Errors.py +++ b/src/engine/SCons/Errors.py @@ -190,14 +190,13 @@ def convert_to_BuildError(status, exc_info=None): # error, which might be different from the target being built # (for example, failure to create the directory in which the # target file will appear). - try: - filename = status.filename - except AttributeError: - filename = None + filename = getattr(status, 'filename', None) + strerror = getattr(status, 'strerror', str(status)) + errno = getattr(status, 'errno', 2) buildError = BuildError( - errstr=status.strerror, - status=status.errno, + errstr=strerror, + status=errno, exitstatus=2, filename=filename, exc_info=exc_info) diff --git a/src/engine/SCons/ErrorsTests.py b/src/engine/SCons/ErrorsTests.py index c38158d..7819580 100644 --- a/src/engine/SCons/ErrorsTests.py +++ b/src/engine/SCons/ErrorsTests.py @@ -23,6 +23,8 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import errno +import os import sys import unittest @@ -100,6 +102,29 @@ class ErrorsTestCase(unittest.TestCase): except SCons.Errors.ExplicitExit as e: assert e.node == "node" + def test_convert_EnvironmentError_to_BuildError(self): + """Test the convert_to_BuildError function on EnvironmentError + exceptions. + """ + ee = SCons.Errors.EnvironmentError("test env error") + be = SCons.Errors.convert_to_BuildError(ee) + assert be.errstr == "test env error" + assert be.status == 2 + assert be.exitstatus == 2 + assert be.filename is None + + def test_convert_OSError_to_BuildError(self): + """Test the convert_to_BuildError function on OSError + exceptions. + """ + ose = OSError(7, 'test oserror') + be = SCons.Errors.convert_to_BuildError(ose) + assert be.errstr == 'test oserror' + assert be.status == 7 + assert be.exitstatus == 2 + assert be.filename is None + + if __name__ == "__main__": suite = unittest.makeSuite(ErrorsTestCase, 'test_') TestUnit.run(suite) -- cgit v0.12 From 7d6468b7bf017ec8b0a412342b15d2a4d940c89d Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Mon, 9 Apr 2018 15:34:24 -0500 Subject: updated yacc tool to add default paths, updated to test to use the tool specifically and know when to skip on windows. --- src/engine/SCons/Tool/yacc.py | 9 +++++++++ test/YACC/YACC.py | 8 ++++---- test/YACC/live-check-output-cleaned.py | 2 +- test/YACC/live.py | 12 +++++++++--- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/engine/SCons/Tool/yacc.py b/src/engine/SCons/Tool/yacc.py index 648433b..be65104 100644 --- a/src/engine/SCons/Tool/yacc.py +++ b/src/engine/SCons/Tool/yacc.py @@ -34,6 +34,7 @@ selection method. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os.path +import sys import SCons.Defaults import SCons.Tool @@ -113,6 +114,14 @@ def generate(env): cxx_file.add_action('.yy', YaccAction) cxx_file.add_emitter('.yy', yyEmitter) + if(sys.platform == 'win32'): + bison = SCons.Tool.find_program_path(env, 'bison', default_paths=['C:\\cygwin64\\bin', 'C:\\cygwin\\bin', 'C:\\msys', 'C:\\msys64' ]) + if bison: + bison_bin_dir = os.path.dirname(bison) + env.AppendENVPath('PATH', bison_bin_dir) + else: + SCons.Warnings.Warning('yacc tool requested, but bison binary not found in ENV PATH') + env['YACC'] = env.Detect('bison') or 'yacc' env['YACCFLAGS'] = SCons.Util.CLVar('') env['YACCCOM'] = '$YACC $YACCFLAGS -o $TARGET $SOURCES' diff --git a/test/YACC/YACC.py b/test/YACC/YACC.py index 3fc1f7c..b27c2a7 100644 --- a/test/YACC/YACC.py +++ b/test/YACC/YACC.py @@ -53,10 +53,10 @@ env.CFile(target = 'ddd', source = 'ddd.ym') test.run(arguments = '.', stderr = None) -test.must_match('aaa.c', "aaa.y\nmyyacc.py\n") -test.must_match('bbb.c', "bbb.yacc\nmyyacc.py\n") -test.must_match('ccc.cc', "ccc.yacc\nmyyacc.py\n") -test.must_match('ddd.m', "ddd.yacc\nmyyacc.py\n") +test.must_match('aaa.c', "aaa.y" + os.linesep + "myyacc.py" + os.linesep) +test.must_match('bbb.c', "bbb.yacc" + os.linesep + "myyacc.py" + os.linesep) +test.must_match('ccc.cc', "ccc.yacc" + os.linesep + "myyacc.py" + os.linesep) +test.must_match('ddd.m', "ddd.yacc" + os.linesep + "myyacc.py" + os.linesep) diff --git a/test/YACC/live-check-output-cleaned.py b/test/YACC/live-check-output-cleaned.py index a6240c9..8329b94 100644 --- a/test/YACC/live-check-output-cleaned.py +++ b/test/YACC/live-check-output-cleaned.py @@ -40,7 +40,7 @@ if not yacc: test.skip_test('No yacc or bison found; skipping test.\n') test.write('SConstruct', """ -foo = Environment(YACCFLAGS='-v -d') +foo = Environment(YACCFLAGS='-v -d', tools = ['default', 'yacc']) foo.CFile(source = 'foo.y') """ % locals()) diff --git a/test/YACC/live.py b/test/YACC/live.py index 6dd08f7..35f6c37 100644 --- a/test/YACC/live.py +++ b/test/YACC/live.py @@ -29,6 +29,8 @@ Test YACC and YACCFLAGS with a live yacc compiler. """ import TestSCons +import sys +import os _exe = TestSCons._exe _python_ = TestSCons._python_ @@ -40,12 +42,16 @@ yacc = test.where_is('yacc') or test.where_is('bison') if not yacc: test.skip_test('No yacc or bison found; skipping test.\n') +if sys.platform == 'win32': + if not test.where_is('gcc'): + test.skip_test('No gcc found on windows; skipping test.\n') + test.file_fixture('wrapper.py') test.write('SConstruct', """ -foo = Environment(YACCFLAGS='-d') +foo = Environment(YACCFLAGS='-d', tools = ['default', 'yacc']) yacc = foo.Dictionary('YACC') -bar = Environment(YACC = r'%(_python_)s wrapper.py ' + yacc) +bar = Environment(YACC = r'%(_python_)s wrapper.py ' + yacc, tools = ['default', 'yacc']) foo.Program(target = 'foo', source = 'foo.y') bar.Program(target = 'bar', source = 'bar.y') foo.Program(target = 'hello', source = ['hello.cpp']) @@ -152,7 +158,7 @@ test.run(arguments = 'bar' + _exe) test.up_to_date(arguments = 'bar' + _exe) -test.must_match(test.workpath('wrapper.out'), "wrapper.py\n") +test.must_match(test.workpath('wrapper.out'), "wrapper.py" + os.linesep) test.run(program = test.workpath('bar'), stdin = "b\n", stdout = "bar.y\n") -- cgit v0.12 From e8ee5e89e0c301324aaa3b78a7a2e8c8919d53e8 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Tue, 10 Apr 2018 08:03:43 -0500 Subject: removed binary read and write so newlines are crossplatform and updated path for appveyor script --- .appveyor.yml | 2 +- test/YACC/YACC-fixture/myyacc.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index a9d2dfc..a735070 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -2,7 +2,7 @@ image: Visual Studio 2017 shallow_clone: true install: - - "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + - "set PATH=%PYTHON%;%PYTHON%\\Scripts;C:\\cygwin64\\bin;C:\\msys64;%PATH%" - python --version - pip install lxml - pip install pypiwin32 diff --git a/test/YACC/YACC-fixture/myyacc.py b/test/YACC/YACC-fixture/myyacc.py index c2e1abf..756c98f 100644 --- a/test/YACC/YACC-fixture/myyacc.py +++ b/test/YACC/YACC-fixture/myyacc.py @@ -4,10 +4,10 @@ cmd_opts, args = getopt.getopt(sys.argv[1:], 'o:', []) output = None opt_string = '' for opt, arg in cmd_opts: - if opt == '-o': output = open(arg, 'wb') + if opt == '-o': output = open(arg, 'w') else: opt_string = opt_string + ' ' + opt for a in args: - contents = open(a, 'rb').read() - output.write(contents.replace(b'YACC', b'myyacc.py')) + contents = open(a, 'r').read() + output.write(contents.replace('YACC', 'myyacc.py')) output.close() sys.exit(0) -- cgit v0.12 From 5f3e6792dbaa74b3c384f9dcbf6ff71e984ba766 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Tue, 10 Apr 2018 11:17:40 -0500 Subject: removed unnecessary parans --- src/engine/SCons/Tool/yacc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/SCons/Tool/yacc.py b/src/engine/SCons/Tool/yacc.py index be65104..a43b3b3 100644 --- a/src/engine/SCons/Tool/yacc.py +++ b/src/engine/SCons/Tool/yacc.py @@ -114,7 +114,7 @@ def generate(env): cxx_file.add_action('.yy', YaccAction) cxx_file.add_emitter('.yy', yyEmitter) - if(sys.platform == 'win32'): + if sys.platform == 'win32': bison = SCons.Tool.find_program_path(env, 'bison', default_paths=['C:\\cygwin64\\bin', 'C:\\cygwin\\bin', 'C:\\msys', 'C:\\msys64' ]) if bison: bison_bin_dir = os.path.dirname(bison) -- cgit v0.12 From f526583e514cb1cbcfd44cd26b95d4025b3ba7f8 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Tue, 10 Apr 2018 12:40:36 -0500 Subject: moved default paths for cywing and mingw to their own platform modules. --- src/engine/SCons/Platform/cygwin.py | 9 +++++++++ src/engine/SCons/Platform/mingw.py | 39 +++++++++++++++++++++++++++++++++++++ src/engine/SCons/Tool/yacc.py | 4 +++- 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/engine/SCons/Platform/mingw.py diff --git a/src/engine/SCons/Platform/cygwin.py b/src/engine/SCons/Platform/cygwin.py index 8b4669c..f6c5086 100644 --- a/src/engine/SCons/Platform/cygwin.py +++ b/src/engine/SCons/Platform/cygwin.py @@ -32,9 +32,18 @@ selection method. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import sys + from . import posix from SCons.Platform import TempFileMunge +CYGWIN_DEFAULT_PATHS = [] +if sys.platform == 'win32': + CYGWIN_DEFAULT_PATHS = [ + r'C:\cygwin64\bin', + r'C:\cygwin\bin' + ] + def generate(env): posix.generate(env) diff --git a/src/engine/SCons/Platform/mingw.py b/src/engine/SCons/Platform/mingw.py new file mode 100644 index 0000000..73633d7 --- /dev/null +++ b/src/engine/SCons/Platform/mingw.py @@ -0,0 +1,39 @@ +"""SCons.Platform.mingw + +Platform-specific initialization for the MinGW system. + +""" + +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import sys + +MINGW_DEFAULT_PATHS = [] +if sys.platform == 'win32': + MINGW_DEFAULT_PATHS = [ + r'C:\msys64', + r'C:\msys' + ] \ No newline at end of file diff --git a/src/engine/SCons/Tool/yacc.py b/src/engine/SCons/Tool/yacc.py index a43b3b3..cd9b9a8 100644 --- a/src/engine/SCons/Tool/yacc.py +++ b/src/engine/SCons/Tool/yacc.py @@ -39,6 +39,8 @@ import sys import SCons.Defaults import SCons.Tool import SCons.Util +from SCons.Platform.mingw import MINGW_DEFAULT_PATHS +from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS YaccAction = SCons.Action.Action("$YACCCOM", "$YACCCOMSTR") @@ -115,7 +117,7 @@ def generate(env): cxx_file.add_emitter('.yy', yyEmitter) if sys.platform == 'win32': - bison = SCons.Tool.find_program_path(env, 'bison', default_paths=['C:\\cygwin64\\bin', 'C:\\cygwin\\bin', 'C:\\msys', 'C:\\msys64' ]) + bison = SCons.Tool.find_program_path(env, 'bison', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) if bison: bison_bin_dir = os.path.dirname(bison) env.AppendENVPath('PATH', bison_bin_dir) -- cgit v0.12 From f8b479702bb0b5c81c147438f9ce91b5b6b460f2 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Tue, 10 Apr 2018 16:08:32 -0500 Subject: updated CHANGES.txt --- src/CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 43efa9e..76487f6 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -8,6 +8,8 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From Daniel Moody: + - Add common location for default paths for cygwin and mingw in Platform modules + - Updated YACC tool to work on windows with Cygwin/MinGW setups - Set the pickling protocal back to highest which was causing issues with variant dir tests. This will cause issues if reading sconsigns pickled with the previous lower protocal. -- cgit v0.12 From e650cc43946737b9d950049ae9964064ea4bf33f Mon Sep 17 00:00:00 2001 From: William Deegan Date: Tue, 10 Apr 2018 19:27:45 -0700 Subject: Fix for issue 3013 (and duplicate 3018). From bitbucket PR: https://bitbucket.org/scons/scons/pull-requests/303 --- src/CHANGES.txt | 3 ++ src/engine/SCons/Taskmaster.py | 9 ++++ test/SideEffect/Issues/3013/files/SConscript | 5 +++ test/SideEffect/Issues/3013/files/SConstruct | 21 ++++++++++ test/SideEffect/Issues/3013/files/test.cpp | 2 + .../Issues/3013/sideffect_with_variantdir.py | 49 ++++++++++++++++++++++ 6 files changed, 89 insertions(+) create mode 100644 test/SideEffect/Issues/3013/files/SConscript create mode 100644 test/SideEffect/Issues/3013/files/SConstruct create mode 100644 test/SideEffect/Issues/3013/files/test.cpp create mode 100644 test/SideEffect/Issues/3013/sideffect_with_variantdir.py diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 64c0893..d084feb 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -7,6 +7,9 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE + From Bernard Blackham: + - Fixed handling of side-effects in task master (fixes #3013). + From Daniel Moody: - Set the pickling protocal back to highest which was causing issues with variant dir tests. This will cause issues if reading sconsigns diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index 17507a6..1522ca2 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -470,14 +470,23 @@ class Task(object): pending_children.discard(t) for p in t.waiting_parents: parents[p] = parents.get(p, 0) + 1 + t.waiting_parents = set() for t in targets: if t.side_effects is not None: for s in t.side_effects: if s.get_state() == NODE_EXECUTING: s.set_state(NODE_NO_STATE) + + # The side-effects may have been transferred to + # NODE_NO_STATE by executed_with{,out}_callbacks, but was + # not taken out of the waiting parents/pending children + # data structures. Check for that now. + if s.get_state() == NODE_NO_STATE and s.waiting_parents: + pending_children.discard(s) for p in s.waiting_parents: parents[p] = parents.get(p, 0) + 1 + s.waiting_parents = set() for p in s.waiting_s_e: if p.ref_count == 0: self.tm.candidates.append(p) diff --git a/test/SideEffect/Issues/3013/files/SConscript b/test/SideEffect/Issues/3013/files/SConscript new file mode 100644 index 0000000..27da7bb --- /dev/null +++ b/test/SideEffect/Issues/3013/files/SConscript @@ -0,0 +1,5 @@ +Import('env') + +primary = env.make_file('output', 'test.cpp') +this_causes_problems = env.SideEffect('output_side_effect', 'output') + diff --git a/test/SideEffect/Issues/3013/files/SConstruct b/test/SideEffect/Issues/3013/files/SConstruct new file mode 100644 index 0000000..d01a4b7 --- /dev/null +++ b/test/SideEffect/Issues/3013/files/SConstruct @@ -0,0 +1,21 @@ +env = Environment() + +def make_file(target, source, env): + with open(str(target[0]), 'w') as f: + f.write('gobldygook') + with open(str(target[0]) + '_side_effect', 'w') as side_effect: + side_effect.write('anything') + +env.Append( + BUILDERS={'make_file': Builder(action=Action(make_file))} +) + +env.objdir = 'build' + +SConscript( + 'SConscript', + variant_dir=env.objdir, + exports={'env':env}, + duplicate=0 +) + diff --git a/test/SideEffect/Issues/3013/files/test.cpp b/test/SideEffect/Issues/3013/files/test.cpp new file mode 100644 index 0000000..27424b0 --- /dev/null +++ b/test/SideEffect/Issues/3013/files/test.cpp @@ -0,0 +1,2 @@ +void some_function() {} + diff --git a/test/SideEffect/Issues/3013/sideffect_with_variantdir.py b/test/SideEffect/Issues/3013/sideffect_with_variantdir.py new file mode 100644 index 0000000..9ca3eca --- /dev/null +++ b/test/SideEffect/Issues/3013/sideffect_with_variantdir.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Test materials for Github Issue 3013 submitted by Stefan Ross: +https://github.com/SCons/scons/issues/3013 +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.dir_fixture('files') + +test.run( + arguments = '-j2' +) + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: + -- cgit v0.12 From 56b55490167e165b70dd591da13468cdace315b8 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Thu, 12 Apr 2018 15:06:44 -0500 Subject: updated gettext tools to work on windows cygwin and mingw environments. --- src/engine/SCons/Tool/GettextCommon.py | 2 +- src/engine/SCons/Tool/gettext_tool.py | 12 ++++++++++++ src/engine/SCons/Tool/msgfmt.py | 14 ++++++++++++++ src/engine/SCons/Tool/msginit.py | 14 ++++++++++++++ src/engine/SCons/Tool/msgmerge.py | 15 ++++++++++++++- src/engine/SCons/Tool/xgettext.py | 14 +++++++++++++- 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/engine/SCons/Tool/GettextCommon.py b/src/engine/SCons/Tool/GettextCommon.py index 3b840a6..2d37def 100644 --- a/src/engine/SCons/Tool/GettextCommon.py +++ b/src/engine/SCons/Tool/GettextCommon.py @@ -447,7 +447,7 @@ def _detect_msgfmt(env): """ Detects *msgmfmt(1)* program. """ if 'MSGFMT' in env: return env['MSGFMT'] - msgfmt = env.Detect('msgfmt'); + msgfmt = env.Detect('msgfmt') if msgfmt: return msgfmt raise SCons.Errors.StopError(MsgfmtNotFound, "Could not detect msgfmt") diff --git a/src/engine/SCons/Tool/gettext_tool.py b/src/engine/SCons/Tool/gettext_tool.py index 6031e49..41bd70b 100644 --- a/src/engine/SCons/Tool/gettext_tool.py +++ b/src/engine/SCons/Tool/gettext_tool.py @@ -27,10 +27,22 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" ############################################################################# def generate(env,**kw): + import sys + import os import SCons.Tool + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + from SCons.Tool.GettextCommon \ import _translate, tool_list for t in tool_list(env['PLATFORM'], env): + if sys.platform == 'win32': + tool = SCons.Tool.find_program_path(env, t, default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) + if tool: + tool_bin_dir = os.path.dirname(tool) + env.AppendENVPath('PATH', tool_bin_dir) + else: + SCons.Warnings.Warning(t + ' tool requested, but binary not found in ENV PATH') env.Tool(t) env.AddMethod(_translate, 'Translate') ############################################################################# diff --git a/src/engine/SCons/Tool/msgfmt.py b/src/engine/SCons/Tool/msgfmt.py index 4fe6afd..3c9dde7 100644 --- a/src/engine/SCons/Tool/msgfmt.py +++ b/src/engine/SCons/Tool/msgfmt.py @@ -75,8 +75,22 @@ def _create_mo_file_builder(env, **kw): ############################################################################# def generate(env,**kw): """ Generate `msgfmt` tool """ + import sys + import os import SCons.Util + import SCons.Tool from SCons.Tool.GettextCommon import _detect_msgfmt + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + + if sys.platform == 'win32': + msgfmt = SCons.Tool.find_program_path(env, 'msgfmt', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) + if msgfmt: + msgfmt_bin_dir = os.path.dirname(msgfmt) + env.AppendENVPath('PATH', msgfmt_bin_dir) + else: + SCons.Warnings.Warning('msgfmt tool requested, but binary not found in ENV PATH') + try: env['MSGFMT'] = _detect_msgfmt(env) except: diff --git a/src/engine/SCons/Tool/msginit.py b/src/engine/SCons/Tool/msginit.py index 39f460d..9e056e7 100644 --- a/src/engine/SCons/Tool/msginit.py +++ b/src/engine/SCons/Tool/msginit.py @@ -77,8 +77,22 @@ def _POInitBuilderWrapper(env, target=None, source=_null, **kw): ############################################################################# def generate(env,**kw): """ Generate the `msginit` tool """ + import sys + import os import SCons.Util + import SCons.Tool from SCons.Tool.GettextCommon import _detect_msginit + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + + if sys.platform == 'win32': + msginit = SCons.Tool.find_program_path(env, 'msginit', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) + if msginit: + msginit_bin_dir = os.path.dirname(msginit) + env.AppendENVPath('PATH', msginit_bin_dir) + else: + SCons.Warnings.Warning('msginit tool requested, but binary not found in ENV PATH') + try: env['MSGINIT'] = _detect_msginit(env) except: diff --git a/src/engine/SCons/Tool/msgmerge.py b/src/engine/SCons/Tool/msgmerge.py index 11d7b48..76661d2 100644 --- a/src/engine/SCons/Tool/msgmerge.py +++ b/src/engine/SCons/Tool/msgmerge.py @@ -68,8 +68,21 @@ def _POUpdateBuilderWrapper(env, target=None, source=_null, **kw): ############################################################################# def generate(env,**kw): - """ Generate the `xgettext` tool """ + """ Generate the `msgmerge` tool """ + import sys + import os + import SCons.Tool from SCons.Tool.GettextCommon import _detect_msgmerge + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + + if sys.platform == 'win32': + msgmerge = SCons.Tool.find_program_path(env, 'msgmerge', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) + if msgmerge: + msgmerge_bin_dir = os.path.dirname(msgmerge) + env.AppendENVPath('PATH', msgmerge_bin_dir) + else: + SCons.Warnings.Warning('msgmerge tool requested, but binary not found in ENV PATH') try: env['MSGMERGE'] = _detect_msgmerge(env) except: diff --git a/src/engine/SCons/Tool/xgettext.py b/src/engine/SCons/Tool/xgettext.py index 2c0ce40..e4a17ca 100644 --- a/src/engine/SCons/Tool/xgettext.py +++ b/src/engine/SCons/Tool/xgettext.py @@ -288,9 +288,21 @@ def _POTUpdateBuilder(env, **kw): ############################################################################# def generate(env, **kw): """ Generate `xgettext` tool """ + import sys + import os import SCons.Util + import SCons.Tool from SCons.Tool.GettextCommon import RPaths, _detect_xgettext - + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + + if sys.platform == 'win32': + xgettext = SCons.Tool.find_program_path(env, 'xgettext', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) + if xgettext: + xgettext_bin_dir = os.path.dirname(xgettext) + env.AppendENVPath('PATH', xgettext_bin_dir) + else: + SCons.Warnings.Warning('xgettext tool requested, but binary not found in ENV PATH') try: env['XGETTEXT'] = _detect_xgettext(env) except: -- cgit v0.12 From 6135c8f37e53ef8aae352893c413d0676d586798 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 13 Apr 2018 11:08:56 -0500 Subject: updated CHANGES.txt --- src/CHANGES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 76487f6..dccf61e 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -8,6 +8,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From Daniel Moody: + - Updated gettext tools to setup default paths for windows with Cygwin/MinGW setups - Add common location for default paths for cygwin and mingw in Platform modules - Updated YACC tool to work on windows with Cygwin/MinGW setups - Set the pickling protocal back to highest which was causing issues -- cgit v0.12 From d2ac0761b978215d2006c4bf892bfb8b2a42172d Mon Sep 17 00:00:00 2001 From: grbd Date: Sun, 15 Apr 2018 12:15:48 +0100 Subject: Added in support for Sconstruct.py --- doc/design/native.xml | 5 +++-- doc/man/scons.xml | 4 ++++ doc/scons.mod | 1 + src/CHANGES.txt | 2 +- src/engine/SCons/Script/Main.py | 2 +- test/SConstruct.py | 9 +++++++++ 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/doc/design/native.xml b/doc/design/native.xml index 1329817..97d5831 100644 --- a/doc/design/native.xml +++ b/doc/design/native.xml @@ -64,8 +64,9 @@ By default, the &SCons; utility searches for a file named - &SConstruct;, &Sconstruct;, &sconstruct;, &SConstruct.py or &sconstruct.py; (in that order) in the - current directory, and reads its configuration from the first file + &SConstruct;, &Sconstruct;, &sconstruct;, &SConstruct.py, &Sconstruct.py + or &sconstruct.py; (in that order) in the current directory, + and reads its configuration from the first file found. A command-line option exists to read a different file name. diff --git a/doc/man/scons.xml b/doc/man/scons.xml index cd6541b..abbed4f 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -107,6 +107,7 @@ searches for a file named Sconstruct, sconstruct, SConstruct.py +Sconstruct.py or sconstruct.py (in that order) in the current directory and reads its @@ -639,6 +640,7 @@ before searching for the Sconstruct, sconstruct, SConstruct.py +Sconstruct.py or sconstruct.py file, or doing anything @@ -655,6 +657,7 @@ except that it will search for Sconstruct, sconstruct, SConstruct.py +Sconstruct.py or sconstruct.py in the specified directory.) @@ -1667,6 +1670,7 @@ scons --tree=all,prune,status target Sconstruct , sconstruct , SConstruct.py +Sconstruct.py or sconstruct.py file is found, and uses that diff --git a/doc/scons.mod b/doc/scons.mod index 1d207c9..974ec02 100644 --- a/doc/scons.mod +++ b/doc/scons.mod @@ -138,6 +138,7 @@ Sconstruct"> sconstruct"> SConstruct.py"> +Sconstruct.py"> sconstruct.py"> .sconsign"> src"> diff --git a/src/CHANGES.txt b/src/CHANGES.txt index be4e0dd..093c628 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -8,7 +8,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From Richard West: - - Add SConstruct.py and sconstruct.py to the search path for the root SConstruct file. + - Add SConstruct.py, Sconstruct.py, sconstruct.py to the search path for the root SConstruct file. Allows easier debugging within Visual Studio From Bernard Blackham: diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index 18c493c..923ef1e 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -623,7 +623,7 @@ def _SConstruct_exists(dirname='', repositories=[], filelist=None): current directory. """ if not filelist: - filelist = ['SConstruct', 'Sconstruct', 'sconstruct', 'SConstruct.py', 'sconstruct.py'] + filelist = ['SConstruct', 'Sconstruct', 'sconstruct', 'SConstruct.py', 'Sconstruct.py', 'sconstruct.py'] for file in filelist: sfile = os.path.join(dirname, file) if os.path.isfile(sfile): diff --git a/test/SConstruct.py b/test/SConstruct.py index 3cf1c02..8bc8617 100644 --- a/test/SConstruct.py +++ b/test/SConstruct.py @@ -48,6 +48,15 @@ test.run(arguments = ".", stdout = test.wrap_stdout(read_str = 'sconstruct.py %s\n' % wpath, build_str = "scons: `.' is up to date.\n")) +test.write('Sconstruct.py', """ +import os +print("sconstruct.py "+os.getcwd()) +""") + +test.run(arguments = ".", + stdout = test.wrap_stdout(read_str = 'sconstruct.py %s\n' % wpath, + build_str = "scons: `.' is up to date.\n")) + test.write('SConstruct.py', """ import os print("SConstruct.py "+os.getcwd()) -- cgit v0.12 From 0e38d6dc4916b33c2f13d76d18ed6e0bf18f316b Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 23 Apr 2018 14:01:33 -0600 Subject: Fix invalid xml entities in docs Signed-off-by: Mats Wichmann --- doc/design/native.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/native.xml b/doc/design/native.xml index 97d5831..0ea4074 100644 --- a/doc/design/native.xml +++ b/doc/design/native.xml @@ -64,7 +64,7 @@ By default, the &SCons; utility searches for a file named - &SConstruct;, &Sconstruct;, &sconstruct;, &SConstruct.py, &Sconstruct.py + &SConstruct;, &Sconstruct;, &sconstruct;, &SConstruct.py;, &Sconstruct.py; or &sconstruct.py; (in that order) in the current directory, and reads its configuration from the first file found. A command-line option exists to read a -- cgit v0.12 From 617cf7099997ada8986a4811d6dda09aeb86b16a Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 23 Apr 2018 14:14:42 -0600 Subject: Update CHANGES.txt as well --- src/CHANGES.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index d13fdec..4c05773 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -7,6 +7,9 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE + From Mats Wichmann: + - xml validity fixes from SConstruct.py change + From Richard West: - Add SConstruct.py, Sconstruct.py, sconstruct.py to the search path for the root SConstruct file. Allows easier debugging within Visual Studio @@ -50,7 +53,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From Mats Wichmann: - Updated manpage scons.xml to fix a nested list problem - - Updated doc terminionly: use prepend instead of append as appropriate + - Updated doc terminiology: use prepend instead of append as appropriate From Jonathon Reinhart: - Replace all instances of `int main()` in C code with `int main(void)`. -- cgit v0.12 From 9ec7ee42da85d5a25fac73c13f668e6ccf7087a2 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 23 Apr 2018 13:48:11 -0600 Subject: Fix wiki references to point to gihub wiki Signed-off-by: Mats Wichmann --- QMTest/test-framework.rst | 2 +- bin/xmlagenda.py | 6 ++++-- src/CHANGES.txt | 5 ++--- src/engine/SCons/Tool/docbook/docs/manual.xml | 2 +- test/Docbook/basedir/htmlchunked/image/manual.xml | 2 +- test/Docbook/basedir/htmlhelp/image/manual.xml | 2 +- test/Docbook/basic/epub/image/manual.xml | 2 +- test/Docbook/basic/html/image/manual.xml | 2 +- test/Docbook/basic/htmlchunked/image/manual.xml | 2 +- test/Docbook/basic/htmlhelp/image/manual.xml | 2 +- test/Docbook/rootname/htmlchunked/image/manual.xml | 2 +- test/Docbook/rootname/htmlhelp/image/manual.xml | 2 +- testing/buildbot.yml | 2 +- www/project_tools.html | 2 +- 14 files changed, 18 insertions(+), 17 deletions(-) diff --git a/QMTest/test-framework.rst b/QMTest/test-framework.rst index 844d99b..d51e5f0 100644 --- a/QMTest/test-framework.rst +++ b/QMTest/test-framework.rst @@ -346,7 +346,7 @@ SCons, it is ``test/packaging/sandbox-test/sandbox-test.py``. For even more examples you should check out one of the external Tools, e.g. the *Qt4* Tool at https://bitbucket.org/dirkbaechle/scons_qt4. Also visit the SCons -Tools Index at http://www.scons.org/wiki/ToolsIndex for a complete +Tools Index at https://github.com/SCons/scons/wiki/ToolsIndex for a complete list of available Tools, though not all may have tests yet. How to convert old tests diff --git a/bin/xmlagenda.py b/bin/xmlagenda.py index fcfe53e..7091ee5 100755 --- a/bin/xmlagenda.py +++ b/bin/xmlagenda.py @@ -1,7 +1,9 @@ #!/usr/bin/env python -# Query the scons.tigris.org database for the issues of interest. -# The typical triage query is found on http://www.scons.org/wiki/BugParty +# Query gihub issue tracker for the issues of interest. +# The typical triage query is found on +# https://github.com/scons/scons/wiki/BugParty +# FIXME: this needs reworking for github, and wiki needs updating # Download the issues from Issuezilla as XML; this creates a file # named 'issues.xml'. Run this script in the dir containing diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 4c05773..119d122 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -7,9 +7,6 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - From Mats Wichmann: - - xml validity fixes from SConstruct.py change - From Richard West: - Add SConstruct.py, Sconstruct.py, sconstruct.py to the search path for the root SConstruct file. Allows easier debugging within Visual Studio @@ -54,6 +51,8 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From Mats Wichmann: - Updated manpage scons.xml to fix a nested list problem - Updated doc terminiology: use prepend instead of append as appropriate + - xml validity fixes from SConstruct.py change + - update wiki links to new github location From Jonathon Reinhart: - Replace all instances of `int main()` in C code with `int main(void)`. diff --git a/src/engine/SCons/Tool/docbook/docs/manual.xml b/src/engine/SCons/Tool/docbook/docs/manual.xml index c129753..64ee925 100644 --- a/src/engine/SCons/Tool/docbook/docs/manual.xml +++ b/src/engine/SCons/Tool/docbook/docs/manual.xml @@ -67,7 +67,7 @@ package's docbook folder to the SCons User's Guide, chap. 19.7 "Where to put your custom Builders and Tools" and -the SCons Tools Wiki page at http://scons.org/wiki/ToolsIndex. +the SCons Tools Wiki page at http://github.com/SCons/scons/wiki/ToolsIndex. diff --git a/test/Docbook/basedir/htmlchunked/image/manual.xml b/test/Docbook/basedir/htmlchunked/image/manual.xml index ca12e0e..067c76e 100644 --- a/test/Docbook/basedir/htmlchunked/image/manual.xml +++ b/test/Docbook/basedir/htmlchunked/image/manual.xml @@ -72,7 +72,7 @@ the SCons Tools Wiki page at http://scons.org/wiki/ToolsIndex. + url="https://github.com/SCons/scons/wiki/ToolsIndex">https://github.com/SCons/scons/wiki/ToolsIndex. diff --git a/test/Docbook/basedir/htmlhelp/image/manual.xml b/test/Docbook/basedir/htmlhelp/image/manual.xml index ca12e0e..067c76e 100644 --- a/test/Docbook/basedir/htmlhelp/image/manual.xml +++ b/test/Docbook/basedir/htmlhelp/image/manual.xml @@ -72,7 +72,7 @@ the SCons Tools Wiki page at http://scons.org/wiki/ToolsIndex. + url="https://github.com/SCons/scons/wiki/ToolsIndex">https://github.com/SCons/scons/wiki/ToolsIndex. diff --git a/test/Docbook/basic/epub/image/manual.xml b/test/Docbook/basic/epub/image/manual.xml index ca12e0e..067c76e 100644 --- a/test/Docbook/basic/epub/image/manual.xml +++ b/test/Docbook/basic/epub/image/manual.xml @@ -72,7 +72,7 @@ the SCons Tools Wiki page at http://scons.org/wiki/ToolsIndex. + url="https://github.com/SCons/scons/wiki/ToolsIndex">https://github.com/SCons/scons/wiki/ToolsIndex. diff --git a/test/Docbook/basic/html/image/manual.xml b/test/Docbook/basic/html/image/manual.xml index ca12e0e..067c76e 100644 --- a/test/Docbook/basic/html/image/manual.xml +++ b/test/Docbook/basic/html/image/manual.xml @@ -72,7 +72,7 @@ the SCons Tools Wiki page at http://scons.org/wiki/ToolsIndex. + url="https://github.com/SCons/scons/wiki/ToolsIndex">https://github.com/SCons/scons/wiki/ToolsIndex. diff --git a/test/Docbook/basic/htmlchunked/image/manual.xml b/test/Docbook/basic/htmlchunked/image/manual.xml index ca12e0e..067c76e 100644 --- a/test/Docbook/basic/htmlchunked/image/manual.xml +++ b/test/Docbook/basic/htmlchunked/image/manual.xml @@ -72,7 +72,7 @@ the SCons Tools Wiki page at http://scons.org/wiki/ToolsIndex. + url="https://github.com/SCons/scons/wiki/ToolsIndex">https://github.com/SCons/scons/wiki/ToolsIndex. diff --git a/test/Docbook/basic/htmlhelp/image/manual.xml b/test/Docbook/basic/htmlhelp/image/manual.xml index ca12e0e..067c76e 100644 --- a/test/Docbook/basic/htmlhelp/image/manual.xml +++ b/test/Docbook/basic/htmlhelp/image/manual.xml @@ -72,7 +72,7 @@ the SCons Tools Wiki page at http://scons.org/wiki/ToolsIndex. + url="https://github.com/SCons/scons/wiki/ToolsIndex">https://github.com/SCons/scons/wiki/ToolsIndex. diff --git a/test/Docbook/rootname/htmlchunked/image/manual.xml b/test/Docbook/rootname/htmlchunked/image/manual.xml index ca12e0e..067c76e 100644 --- a/test/Docbook/rootname/htmlchunked/image/manual.xml +++ b/test/Docbook/rootname/htmlchunked/image/manual.xml @@ -72,7 +72,7 @@ the SCons Tools Wiki page at http://scons.org/wiki/ToolsIndex. + url="https://github.com/SCons/scons/wiki/ToolsIndex">https://github.com/SCons/scons/wiki/ToolsIndex. diff --git a/test/Docbook/rootname/htmlhelp/image/manual.xml b/test/Docbook/rootname/htmlhelp/image/manual.xml index ca12e0e..067c76e 100644 --- a/test/Docbook/rootname/htmlhelp/image/manual.xml +++ b/test/Docbook/rootname/htmlhelp/image/manual.xml @@ -72,7 +72,7 @@ the SCons Tools Wiki page at http://scons.org/wiki/ToolsIndex. + url="https://github.com/SCons/scons/wiki/ToolsIndex">https://github.com/SCons/scons/wiki/ToolsIndex. diff --git a/testing/buildbot.yml b/testing/buildbot.yml index 81c2143..7e6bb1a 100644 --- a/testing/buildbot.yml +++ b/testing/buildbot.yml @@ -9,7 +9,7 @@ # ansible-playbook -i hosts buildbot.yml -e 'botuser=sconsy' # # Tested with Ansible 1.5.0, based on -# http://scons.org/wiki/InstallingBuildbotSlaves +# https://github.com/SCons/scons/wiki/InstallingBuildbotSlaves # Send questions to: # # anatoly techtonik diff --git a/www/project_tools.html b/www/project_tools.html index ee731a4..cf80234 100644 --- a/www/project_tools.html +++ b/www/project_tools.html @@ -12,7 +12,7 @@ The main changes are: * Link to our Roadmap page. -->
  • SCons Home Page
  • -
  • SCons Wiki
  • +
  • SCons Wiki
  • -- cgit v0.12 From 649ea1bfa22f40b4ea4c5da7b8210b1efe2062b4 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 23 Apr 2018 12:38:07 -0600 Subject: Change bug references from tigris -> github Signed-off-by: Mats Wichmann --- src/CHANGES.txt | 1 + src/engine/SCons/Platform/win32.py | 2 +- src/engine/SCons/Tool/JavaCommon.py | 2 +- src/engine/SCons/Tool/msvc.py | 2 +- test/CPPDEFINES/append.py | 6 +++--- test/CPPDEFINES/pkg-config.py | 4 ++-- test/D/Issues/2994/Common/D_changed_DFLAGS_not_rebuilding.py | 4 ++-- test/Depends/no-Builder.py | 2 +- test/ExecuteInvalidateCache.py | 2 +- test/Fortran/USE-MODULE-CASEINSENS.py | 2 +- test/Java/Java-1.4.py | 2 +- test/Java/Java-1.5.py | 2 +- test/Java/Java-1.6.py | 2 +- test/Java/swig-dependencies.py | 2 +- test/Libs/SharedLibrary-update-deps.py | 2 +- test/MSVC/PCH-source.py | 2 +- test/Scanner/Dir.py | 2 +- test/VariantDir/include-subdir.py | 2 +- test/option/tree-lib.py | 2 +- 19 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 119d122..ee4d7bb 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -53,6 +53,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - Updated doc terminiology: use prepend instead of append as appropriate - xml validity fixes from SConstruct.py change - update wiki links to new github location + - update bug links to new github location From Jonathon Reinhart: - Replace all instances of `int main()` in C code with `int main(void)`. diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py index 0e9e01f..5c877b5 100644 --- a/src/engine/SCons/Platform/win32.py +++ b/src/engine/SCons/Platform/win32.py @@ -132,7 +132,7 @@ try: # Without this, python can randomly crash while using -jN. # See the python bug at http://bugs.python.org/issue6476 # and SCons issue at - # http://scons.tigris.org/issues/show_bug.cgi?id=2449 + # https://github.com/SCons/scons/issues/2449 def spawnve(mode, file, args, env): spawn_lock.acquire() try: diff --git a/src/engine/SCons/Tool/JavaCommon.py b/src/engine/SCons/Tool/JavaCommon.py index 8b13f9f..dfb9e33 100644 --- a/src/engine/SCons/Tool/JavaCommon.py +++ b/src/engine/SCons/Tool/JavaCommon.py @@ -245,7 +245,7 @@ if java_parsing: # If that's an inner class which is declared in a method, it # requires an index prepended to the class-name, e.g. # 'Foo$1Inner' - # http://scons.tigris.org/issues/show_bug.cgi?id=2087 + # https://github.com/SCons/scons/issues/2087 if self.outer_state.localClasses and \ self.outer_state.stackBrackets[-1] > \ self.outer_state.stackBrackets[-2]+1: diff --git a/src/engine/SCons/Tool/msvc.py b/src/engine/SCons/Tool/msvc.py index f078c62..1412cf7 100644 --- a/src/engine/SCons/Tool/msvc.py +++ b/src/engine/SCons/Tool/msvc.py @@ -112,7 +112,7 @@ def object_emitter(target, source, env, parent_emitter): # # See issue #2505 for a discussion of what to do if it turns # out this assumption causes trouble in the wild: - # http://scons.tigris.org/issues/show_bug.cgi?id=2505 + # https://github.com/SCons/scons/issues/2505 if 'PCH' in env: pch = env['PCH'] if str(target[0]) != SCons.Util.splitext(str(pch))[0] + '.obj': diff --git a/test/CPPDEFINES/append.py b/test/CPPDEFINES/append.py index 7973f22..874fceb 100644 --- a/test/CPPDEFINES/append.py +++ b/test/CPPDEFINES/append.py @@ -43,7 +43,7 @@ env_1738_2.Append(CPPDEFINES={'value' : '1'}) print(env_1738_2.subst('$_CPPDEFFLAGS')) #env_1738_2.Object('test_1738_2', 'main.c') -# http://scons.tigris.org/issues/show_bug.cgi?id=2300 +# https://github.com/SCons/scons/issues/2300 env_2300_1 = Environment(CPPDEFINES = 'foo', CPPDEFPREFIX='-D') env_2300_1.Append(CPPDEFINES='bar') print(env_2300_1.subst('$_CPPDEFFLAGS')) @@ -52,8 +52,8 @@ env_2300_2 = Environment(CPPDEFINES = ['foo'], CPPDEFPREFIX='-D') # note the lis env_2300_2.Append(CPPDEFINES='bar') print(env_2300_2.subst('$_CPPDEFFLAGS')) -# http://scons.tigris.org/issues/show_bug.cgi?id=1152 -# http://scons.tigris.org/issues/show_bug.cgi?id=2900 +# https://github.com/SCons/scons/issues/1152 +# https://github.com/SCons/scons/issues/2900 # Python3 dicts dont preserve order. Hence we supply subclass of OrderedDict # whose __str__ and __repr__ act like a normal dict. from collections import OrderedDict diff --git a/test/CPPDEFINES/pkg-config.py b/test/CPPDEFINES/pkg-config.py index 42f38b6..4096934 100644 --- a/test/CPPDEFINES/pkg-config.py +++ b/test/CPPDEFINES/pkg-config.py @@ -70,7 +70,7 @@ class OrderedPrintingDict(OrderedDict): def __semi_deepcopy__(self): return self.copy() """ + """ -# http://scons.tigris.org/issues/show_bug.cgi?id=2671 +# https://github.com/SCons/scons/issues/2671 # Passing test cases env_1 = Environment(CPPDEFINES=[('DEBUG','1'), 'TEST']) env_1.ParseConfig('PKG_CONFIG_PATH=. %(pkg_config_path)s --cflags bug') @@ -89,7 +89,7 @@ env_4 = Environment(CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None) env_4.MergeFlags('-DSOMETHING -DVARIABLE=2') print(env_4.subst('$_CPPDEFFLAGS')) -# http://scons.tigris.org/issues/show_bug.cgi?id=1738 +# https://github.com/SCons/scons/issues/1738 env_1738_1 = Environment(tools=['default']) env_1738_1.ParseConfig('PKG_CONFIG_PATH=. %(pkg_config_path)s --cflags --libs bug') env_1738_1.Append(CPPDEFINES={'value' : '1'}) diff --git a/test/D/Issues/2994/Common/D_changed_DFLAGS_not_rebuilding.py b/test/D/Issues/2994/Common/D_changed_DFLAGS_not_rebuilding.py index 7e69f50..07b1366 100644 --- a/test/D/Issues/2994/Common/D_changed_DFLAGS_not_rebuilding.py +++ b/test/D/Issues/2994/Common/D_changed_DFLAGS_not_rebuilding.py @@ -1,6 +1,6 @@ """ -Test to check for issue reported in tigris bug 2994 -http://scons.tigris.org/issues/show_bug.cgi?id=2994 +Test to check for issue reported in github issue 2994 +https://github.com/SCons/scons/issues/2994 """ # diff --git a/test/Depends/no-Builder.py b/test/Depends/no-Builder.py index 48ab724..f55fcf9 100644 --- a/test/Depends/no-Builder.py +++ b/test/Depends/no-Builder.py @@ -40,7 +40,7 @@ file2 = File('file2') env.Depends(file1, [[file2, 'file3']]) # Verify that a "hidden" file created by another action causes the # action to run when an explicit Dependency is specified. -# See http://scons.tigris.org/issues/show_bug.cgi?id=2647 +# https://github.com/SCons/scons/issues/2647 env.Depends('hidden', 'file4.out') env.Command('file4.out', 'file4.in', [Copy('$TARGET', '$SOURCE'), Touch('hidden')]) diff --git a/test/ExecuteInvalidateCache.py b/test/ExecuteInvalidateCache.py index acc7701..f6ed391 100644 --- a/test/ExecuteInvalidateCache.py +++ b/test/ExecuteInvalidateCache.py @@ -27,7 +27,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Test the Execute() functions clears the memoized values of affected target Nodes when used with Delete(). Derived from -http://scons.tigris.org/issues/show_bug.cgi?id=1307 +https://github.com/SCons/scons/issues/1307 """ import TestSCons diff --git a/test/Fortran/USE-MODULE-CASEINSENS.py b/test/Fortran/USE-MODULE-CASEINSENS.py index 44c03fe..4dd115b 100644 --- a/test/Fortran/USE-MODULE-CASEINSENS.py +++ b/test/Fortran/USE-MODULE-CASEINSENS.py @@ -27,7 +27,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" # This test tests whether a file that defines a module "a" and # then uses it with a different case ("A") works. Pre-2.0, this # gave a spurious dependency cycle error. -# See http://scons.tigris.org/issues/show_bug.cgi?id=2574 +# See https://github.com/SCons/scons/issues/2574 import TestSCons diff --git a/test/Java/Java-1.4.py b/test/Java/Java-1.4.py index 4076783..8bbefaf 100644 --- a/test/Java/Java-1.4.py +++ b/test/Java/Java-1.4.py @@ -267,7 +267,7 @@ class Foo { } """) # Test private inner class instantiation, courtesy Tilo Prutz: -# http://scons.tigris.org/issues/show_bug.cgi?id=1594 +# https://github.com/SCons/scons/issues/1594 test.write(['src6', 'TestSCons.java'], """\ class test { diff --git a/test/Java/Java-1.5.py b/test/Java/Java-1.5.py index 6659a16..58513e2 100644 --- a/test/Java/Java-1.5.py +++ b/test/Java/Java-1.5.py @@ -267,7 +267,7 @@ class Foo { } """) # Test private inner class instantiation, courtesy Tilo Prutz: -# http://scons.tigris.org/issues/show_bug.cgi?id=1594 +# https://github.com/SCons/scons/issues/1594 test.write(['src6', 'TestSCons.java'], """\ class test { diff --git a/test/Java/Java-1.6.py b/test/Java/Java-1.6.py index be46919..04a9155 100644 --- a/test/Java/Java-1.6.py +++ b/test/Java/Java-1.6.py @@ -267,7 +267,7 @@ class Foo { } """) # Test private inner class instantiation, courtesy Tilo Prutz: -# http://scons.tigris.org/issues/show_bug.cgi?id=1594 +# https://github.com/SCons/scons/issues/1594 test.write(['src6', 'TestSCons.java'], """\ class test { diff --git a/test/Java/swig-dependencies.py b/test/Java/swig-dependencies.py index c72c44a..bd7a576 100644 --- a/test/Java/swig-dependencies.py +++ b/test/Java/swig-dependencies.py @@ -130,7 +130,7 @@ except: # Bug ticket reported also this seems work fine when running outsite # the test framework test.skip_test('Throwing no result for this test because of bug ' + - 'related here: http://scons.tigris.org/issues/show_bug.cgi?id=2907\n') + 'related here: https://github.com/SCons/scons/issues/2907\n') pass #test.must_exist(['java', 'classes', 'foopack', 'foopack.class']) #test.must_exist(['java', 'classes', 'foopack', 'foopackJNI.class']) diff --git a/test/Libs/SharedLibrary-update-deps.py b/test/Libs/SharedLibrary-update-deps.py index 076e3ad..3abce83 100644 --- a/test/Libs/SharedLibrary-update-deps.py +++ b/test/Libs/SharedLibrary-update-deps.py @@ -26,7 +26,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Test that SharedLibrary() updates when a different lib is linked, even if it has the same md5. -This is http://scons.tigris.org/issues/show_bug.cgi?id=2903 +This is https://github.com/SCons/scons/issues/2903 """ import sys diff --git a/test/MSVC/PCH-source.py b/test/MSVC/PCH-source.py index 6015fec..ccab66d 100644 --- a/test/MSVC/PCH-source.py +++ b/test/MSVC/PCH-source.py @@ -28,7 +28,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" Test use of pre-compiled headers when the source .cpp file shows up in both the env.PCH() and the env.Program() source list. -Issue 2505: http://scons.tigris.org/issues/show_bug.cgi?id=2505 +Issue 2505: http://github.com/SCons/scons/issues/2505 """ import TestSCons diff --git a/test/Scanner/Dir.py b/test/Scanner/Dir.py index 86b80e9..fa4f6c9 100644 --- a/test/Scanner/Dir.py +++ b/test/Scanner/Dir.py @@ -27,7 +27,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Verify that a simple scanner that returns Dir nodes works correctly. -Submitted as http://scons.tigris.org/issues/show_bug.cgi?id=2534 +Submitted as https://github.com/SCons/scons/issues/2534 """ import TestSCons diff --git a/test/VariantDir/include-subdir.py b/test/VariantDir/include-subdir.py index d616bba..fe26c0a 100644 --- a/test/VariantDir/include-subdir.py +++ b/test/VariantDir/include-subdir.py @@ -32,7 +32,7 @@ we have to make sure that the file gets copied to the variant dir. (This was not the case for 0.98.5 and earlier) Test case supplied by Jared Grubb, based on a minimal example supplied -by Ali Tofigh, filed as http://scons.tigris.org/issues/show_bug.cgi?id=2121 +by Ali Tofigh, filed as https://github.com/SCons/scons/issues/2121 """ import TestSCons diff --git a/test/option/tree-lib.py b/test/option/tree-lib.py index 2dc5fb0..4725d42 100644 --- a/test/option/tree-lib.py +++ b/test/option/tree-lib.py @@ -30,7 +30,7 @@ the dependency on the library. (On earlier versions of the Microsoft toolchain this wouldn't show up unless the library already existed on disk.) -Issue 1363: http://scons.tigris.org/issues/show_bug.cgi?id=1363 +Issue 1363: https://github.com/SCons/scons/issues/1363 """ import TestSCons -- cgit v0.12 From f095eb14013f1f3e21dbb213b4e43694ecbf091d Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 29 Apr 2018 21:10:43 -0400 Subject: move test files from QMTest to testing/framework. QMtest hasn't been used in quite some time --- QMTest/README.txt | 50 - QMTest/SConscript | 61 - QMTest/TestCmd.py | 1907 ------------- QMTest/TestCmdTests.py | 3419 ----------------------- QMTest/TestCommon.py | 748 ----- QMTest/TestCommonTests.py | 2340 ---------------- QMTest/TestRuntest.py | 167 -- QMTest/TestSCons.py | 1642 ----------- QMTest/TestSConsMSVS.py | 1177 -------- QMTest/TestSCons_time.py | 331 --- QMTest/TestSConsign.py | 90 - QMTest/test-framework.rst | 430 --- SConstruct | 14 +- bin/SConsExamples.py | 4 +- bin/time-scons.py | 2 +- bin/update-release-info.py | 10 +- runtest.py | 8 +- src/CHANGES.txt | 2 + src/test_strings.py | 10 +- test/D/Support/executablesSearch.py | 2 +- test/README | 4 +- test/update-release-info/update-release-info.py | 2 +- testing/framework/README.txt | 50 + testing/framework/SConscript | 61 + testing/framework/TestCmd.py | 1907 +++++++++++++ testing/framework/TestCmdTests.py | 3419 +++++++++++++++++++++++ testing/framework/TestCommon.py | 748 +++++ testing/framework/TestCommonTests.py | 2340 ++++++++++++++++ testing/framework/TestRuntest.py | 167 ++ testing/framework/TestSCons.py | 1642 +++++++++++ testing/framework/TestSConsMSVS.py | 1177 ++++++++ testing/framework/TestSCons_time.py | 331 +++ testing/framework/TestSConsign.py | 90 + testing/framework/test-framework.rst | 427 +++ timings/README.txt | 4 +- 35 files changed, 12389 insertions(+), 12394 deletions(-) delete mode 100644 QMTest/README.txt delete mode 100644 QMTest/SConscript delete mode 100644 QMTest/TestCmd.py delete mode 100644 QMTest/TestCmdTests.py delete mode 100644 QMTest/TestCommon.py delete mode 100644 QMTest/TestCommonTests.py delete mode 100644 QMTest/TestRuntest.py delete mode 100644 QMTest/TestSCons.py delete mode 100644 QMTest/TestSConsMSVS.py delete mode 100644 QMTest/TestSCons_time.py delete mode 100644 QMTest/TestSConsign.py delete mode 100644 QMTest/test-framework.rst create mode 100644 testing/framework/README.txt create mode 100644 testing/framework/SConscript create mode 100644 testing/framework/TestCmd.py create mode 100644 testing/framework/TestCmdTests.py create mode 100644 testing/framework/TestCommon.py create mode 100644 testing/framework/TestCommonTests.py create mode 100644 testing/framework/TestRuntest.py create mode 100644 testing/framework/TestSCons.py create mode 100644 testing/framework/TestSConsMSVS.py create mode 100644 testing/framework/TestSCons_time.py create mode 100644 testing/framework/TestSConsign.py create mode 100644 testing/framework/test-framework.rst diff --git a/QMTest/README.txt b/QMTest/README.txt deleted file mode 100644 index 7750bc7..0000000 --- a/QMTest/README.txt +++ /dev/null @@ -1,50 +0,0 @@ -This directory contains testing infrastructure. Note that not all of -the pieces here are local to SCons. - - README.txt - - What you're looking at right now. - - SConscript - - Configuration for our packaging build, to copy the necessary - parts of the infrastructure into a build directory. - - TestCmd.py - TestCmdTests.py - TestCommon.py - TestCommonTests.py - - The TestCmd infrastructure for testing external commands. - These are for generic command testing, are used by some - other projects, and are developed separately from SCons. - (They're developed by SK, but still...) - - We've captured the unit tests (Test*Tests.py) for these files - along with the actual modules themselves to make it a little - easier to hack on them for our purposes. Note, however, - that any SCons-specific functionality should be implemented - in one of the - - TestRuntest.py - - Test infrastructure for our runtest.py script. - - TestSCons.py - - Test infrastructure for SCons itself. - - TestSConsMSVS.py - - Test infrastructure for SCons' Visual Studio support. - - TestSCons_time.py - - Test infrastructure for the scons-time.py script. - - TestSConsign.py - - Test infrastructure for the sconsign.py script. - -__COPYRIGHT__ -__FILE__ __REVISION__ __DATE__ __DEVELOPER__ diff --git a/QMTest/SConscript b/QMTest/SConscript deleted file mode 100644 index 1db7301..0000000 --- a/QMTest/SConscript +++ /dev/null @@ -1,61 +0,0 @@ -# -# SConscript file for external packages we need. -# - -# -# __COPYRIGHT__ -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY -# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# - -import os.path - -Import('build_dir', 'env') - -files = [ - 'TestCmd.py', - 'TestCommon.py', - 'TestRuntest.py', - 'TestSCons.py', - 'TestSConsign.py', - 'TestSCons_time.py', -] - -def copy(target, source, env): - t = str(target[0]) - s = str(source[0]) - c = open(s, 'r').read() - # Note: We construct the __ VERSION __ substitution string at - # run-time so it doesn't get replaced when this file gets copied - # into the tree for packaging. - c = c.replace('__' + 'VERSION' + '__', env['VERSION']) - open(t, 'w').write(c) - -for file in files: - # Guarantee that real copies of these files always exist in - # build/QMTest. If there's a symlink there, then this is an Aegis - # build and we blow them away now so that they'll get "built" later. - p = os.path.join(build_dir, 'QMTest', file) - if os.path.islink(p): - os.unlink(p) - if not os.path.isabs(p): - p = '#' + p - env.Command(p, file, copy) - Local(p) diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py deleted file mode 100644 index 0aab9a8..0000000 --- a/QMTest/TestCmd.py +++ /dev/null @@ -1,1907 +0,0 @@ -""" -TestCmd.py: a testing framework for commands and scripts. - -The TestCmd module provides a framework for portable automated testing -of executable commands and scripts (in any language, not just Python), -especially commands and scripts that require file system interaction. - -In addition to running tests and evaluating conditions, the TestCmd -module manages and cleans up one or more temporary workspace -directories, and provides methods for creating files and directories in -those workspace directories from in-line data, here-documents), allowing -tests to be completely self-contained. - -A TestCmd environment object is created via the usual invocation: - - import TestCmd - test = TestCmd.TestCmd() - -There are a bunch of keyword arguments available at instantiation: - - test = TestCmd.TestCmd(description = 'string', - program = 'program_or_script_to_test', - interpreter = 'script_interpreter', - workdir = 'prefix', - subdir = 'subdir', - verbose = Boolean, - match = default_match_function, - match_stdout = default_match_stdout_function, - match_stderr = default_match_stderr_function, - diff = default_diff_stderr_function, - diff_stdout = default_diff_stdout_function, - diff_stderr = default_diff_stderr_function, - combine = Boolean) - -There are a bunch of methods that let you do different things: - - test.verbose_set(1) - - test.description_set('string') - - test.program_set('program_or_script_to_test') - - test.interpreter_set('script_interpreter') - test.interpreter_set(['script_interpreter', 'arg']) - - test.workdir_set('prefix') - test.workdir_set('') - - test.workpath('file') - test.workpath('subdir', 'file') - - test.subdir('subdir', ...) - - test.rmdir('subdir', ...) - - test.write('file', "contents\n") - test.write(['subdir', 'file'], "contents\n") - - test.read('file') - test.read(['subdir', 'file']) - test.read('file', mode) - test.read(['subdir', 'file'], mode) - - test.writable('dir', 1) - test.writable('dir', None) - - test.preserve(condition, ...) - - test.cleanup(condition) - - test.command_args(program = 'program_or_script_to_run', - interpreter = 'script_interpreter', - arguments = 'arguments to pass to program') - - test.run(program = 'program_or_script_to_run', - interpreter = 'script_interpreter', - arguments = 'arguments to pass to program', - chdir = 'directory_to_chdir_to', - stdin = 'input to feed to the program\n') - universal_newlines = True) - - p = test.start(program = 'program_or_script_to_run', - interpreter = 'script_interpreter', - arguments = 'arguments to pass to program', - universal_newlines = None) - - test.finish(self, p) - - test.pass_test() - test.pass_test(condition) - test.pass_test(condition, function) - - test.fail_test() - test.fail_test(condition) - test.fail_test(condition, function) - test.fail_test(condition, function, skip) - test.fail_test(condition, function, skip, message) - - test.no_result() - test.no_result(condition) - test.no_result(condition, function) - test.no_result(condition, function, skip) - - test.stdout() - test.stdout(run) - - test.stderr() - test.stderr(run) - - test.symlink(target, link) - - test.banner(string) - test.banner(string, width) - - test.diff(actual, expected) - - test.diff_stderr(actual, expected) - - test.diff_stdout(actual, expected) - - test.match(actual, expected) - - test.match_stderr(actual, expected) - - test.match_stdout(actual, expected) - - test.set_match_function(match, stdout, stderr) - - test.match_exact("actual 1\nactual 2\n", "expected 1\nexpected 2\n") - test.match_exact(["actual 1\n", "actual 2\n"], - ["expected 1\n", "expected 2\n"]) - test.match_caseinsensitive("Actual 1\nACTUAL 2\n", "expected 1\nEXPECTED 2\n") - - test.match_re("actual 1\nactual 2\n", regex_string) - test.match_re(["actual 1\n", "actual 2\n"], list_of_regexes) - - test.match_re_dotall("actual 1\nactual 2\n", regex_string) - test.match_re_dotall(["actual 1\n", "actual 2\n"], list_of_regexes) - - test.tempdir() - test.tempdir('temporary-directory') - - test.sleep() - test.sleep(seconds) - - test.where_is('foo') - test.where_is('foo', 'PATH1:PATH2') - test.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4') - - test.unlink('file') - test.unlink('subdir', 'file') - -The TestCmd module provides pass_test(), fail_test(), and no_result() -unbound functions that report test results for use with the Aegis change -management system. These methods terminate the test immediately, -reporting PASSED, FAILED, or NO RESULT respectively, and exiting with -status 0 (success), 1 or 2 respectively. This allows for a distinction -between an actual failed test and a test that could not be properly -evaluated because of an external condition (such as a full file system -or incorrect permissions). - - import TestCmd - - TestCmd.pass_test() - TestCmd.pass_test(condition) - TestCmd.pass_test(condition, function) - - TestCmd.fail_test() - TestCmd.fail_test(condition) - TestCmd.fail_test(condition, function) - TestCmd.fail_test(condition, function, skip) - TestCmd.fail_test(condition, function, skip, message) - - TestCmd.no_result() - TestCmd.no_result(condition) - TestCmd.no_result(condition, function) - TestCmd.no_result(condition, function, skip) - -The TestCmd module also provides unbound global functions that handle -matching in the same way as the match_*() methods described above. - - import TestCmd - - test = TestCmd.TestCmd(match = TestCmd.match_exact) - - test = TestCmd.TestCmd(match = TestCmd.match_caseinsensitive) - - test = TestCmd.TestCmd(match = TestCmd.match_re) - - test = TestCmd.TestCmd(match = TestCmd.match_re_dotall) - -These functions are also available as static methods: - - import TestCmd - - test = TestCmd.TestCmd(match = TestCmd.TestCmd.match_exact) - - test = TestCmd.TestCmd(match = TestCmd.TestCmd.match_caseinsensitive) - - test = TestCmd.TestCmd(match = TestCmd.TestCmd.match_re) - - test = TestCmd.TestCmd(match = TestCmd.TestCmd.match_re_dotall) - -These static methods can be accessed by a string naming the method: - - import TestCmd - - test = TestCmd.TestCmd(match = 'match_exact') - - test = TestCmd.TestCmd(match = 'match_caseinsensitive') - - test = TestCmd.TestCmd(match = 'match_re') - - test = TestCmd.TestCmd(match = 'match_re_dotall') - -The TestCmd module provides unbound global functions that can be used -for the "diff" argument to TestCmd.TestCmd instantiation: - - import TestCmd - - test = TestCmd.TestCmd(match = TestCmd.match_re, - diff = TestCmd.diff_re) - - test = TestCmd.TestCmd(diff = TestCmd.simple_diff) - - test = TestCmd.TestCmd(diff = TestCmd.context_diff) - - test = TestCmd.TestCmd(diff = TestCmd.unified_diff) - -These functions are also available as static methods: - - import TestCmd - - test = TestCmd.TestCmd(match = TestCmd.TestCmd.match_re, - diff = TestCmd.TestCmd.diff_re) - - test = TestCmd.TestCmd(diff = TestCmd.TestCmd.simple_diff) - - test = TestCmd.TestCmd(diff = TestCmd.TestCmd.context_diff) - - test = TestCmd.TestCmd(diff = TestCmd.TestCmd.unified_diff) - -These static methods can be accessed by a string naming the method: - - import TestCmd - - test = TestCmd.TestCmd(match = 'match_re', diff = 'diff_re') - - test = TestCmd.TestCmd(diff = 'simple_diff') - - test = TestCmd.TestCmd(diff = 'context_diff') - - test = TestCmd.TestCmd(diff = 'unified_diff') - -The "diff" argument can also be used with standard difflib functions: - - import difflib - - test = TestCmd.TestCmd(diff = difflib.context_diff) - - test = TestCmd.TestCmd(diff = difflib.unified_diff) - -Lastly, the where_is() method also exists in an unbound function -version. - - import TestCmd - - TestCmd.where_is('foo') - TestCmd.where_is('foo', 'PATH1:PATH2') - TestCmd.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4') -""" - -# Copyright 2000-2010 Steven Knight -# This module is free software, and you may redistribute it and/or modify -# it under the same terms as Python itself, so long as this copyright message -# and disclaimer are retained in their original form. -# -# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -# THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. -# -# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -# AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -from __future__ import division, print_function - -__author__ = "Steven Knight " -__revision__ = "TestCmd.py 1.3.D001 2010/06/03 12:58:27 knight" -__version__ = "1.3" - -import atexit -import difflib -import errno -import os -import re -import shutil -import signal -import stat -import sys -import tempfile -import threading -import time -import traceback -import types - - -IS_PY3 = sys.version_info[0] == 3 -IS_WINDOWS = sys.platform == 'win32' - - -class null(object): - pass - - -_Null = null() - -try: - from collections import UserList, UserString -except ImportError: - # no 'collections' module or no UserFoo in collections - exec('from UserList import UserList') - exec('from UserString import UserString') - -__all__ = [ - 'diff_re', - 'fail_test', - 'no_result', - 'pass_test', - 'match_exact', - 'match_caseinsensitive', - 'match_re', - 'match_re_dotall', - 'python', - '_python_', - 'TestCmd', - 'to_bytes', - 'to_str', -] - - -def is_List(e): - return isinstance(e, (list, UserList)) - - -def to_bytes(s): - if isinstance(s, bytes) or bytes is str: - return s - return bytes(s, 'utf-8') - - -def to_str(s): - if bytes is str or is_String(s): - return s - return str(s, 'utf-8') - - -try: - eval('unicode') -except NameError: - def is_String(e): - return isinstance(e, (str, UserString)) -else: - def is_String(e): - return isinstance(e, (str, unicode, UserString)) - -tempfile.template = 'testcmd.' -if os.name in ('posix', 'nt'): - tempfile.template = 'testcmd.' + str(os.getpid()) + '.' -else: - tempfile.template = 'testcmd.' - -re_space = re.compile('\s') - - -def _caller(tblist, skip): - string = "" - arr = [] - for file, line, name, text in tblist: - if file[-10:] == "TestCmd.py": - break - arr = [(file, line, name, text)] + arr - atfrom = "at" - for file, line, name, text in arr[skip:]: - if name in ("?", ""): - name = "" - else: - name = " (" + name + ")" - string = string + ("%s line %d of %s%s\n" % (atfrom, line, file, name)) - atfrom = "\tfrom" - return string - - -def fail_test(self=None, condition=1, function=None, skip=0, message=None): - """Cause the test to fail. - - By default, the fail_test() method reports that the test FAILED - and exits with a status of 1. If a condition argument is supplied, - the test fails only if the condition is true. - """ - if not condition: - return - if not function is None: - function() - of = "" - desc = "" - sep = " " - if not self is None: - if self.program: - of = " of " + self.program - sep = "\n\t" - if self.description: - desc = " [" + self.description + "]" - sep = "\n\t" - - at = _caller(traceback.extract_stack(), skip) - if message: - msg = "\t%s\n" % message - else: - msg = "" - sys.stderr.write("FAILED test" + of + desc + sep + at + msg) - - sys.exit(1) - - -def no_result(self=None, condition=1, function=None, skip=0): - """Causes a test to exit with no valid result. - - By default, the no_result() method reports NO RESULT for the test - and exits with a status of 2. If a condition argument is supplied, - the test fails only if the condition is true. - """ - if not condition: - return - if not function is None: - function() - of = "" - desc = "" - sep = " " - if not self is None: - if self.program: - of = " of " + self.program - sep = "\n\t" - if self.description: - desc = " [" + self.description + "]" - sep = "\n\t" - - at = _caller(traceback.extract_stack(), skip) - sys.stderr.write("NO RESULT for test" + of + desc + sep + at) - - sys.exit(2) - - -def pass_test(self=None, condition=1, function=None): - """Causes a test to pass. - - By default, the pass_test() method reports PASSED for the test - and exits with a status of 0. If a condition argument is supplied, - the test passes only if the condition is true. - """ - if not condition: - return - if not function is None: - function() - sys.stderr.write("PASSED\n") - sys.exit(0) - - -def match_exact(lines=None, matches=None, newline=os.sep): - """ - """ - - if isinstance(lines, bytes) or bytes is str: - newline = to_bytes(newline) - - if not is_List(lines): - lines = lines.split(newline) - if not is_List(matches): - matches = matches.split(newline) - if len(lines) != len(matches): - return - for i in range(len(lines)): - if lines[i] != matches[i]: - return - return 1 - - -def match_caseinsensitive(lines=None, matches=None): - """ - """ - if not is_List(lines): - lines = lines.split("\n") - if not is_List(matches): - matches = matches.split("\n") - if len(lines) != len(matches): - return - for i in range(len(lines)): - if lines[i].lower() != matches[i].lower(): - return - return 1 - - -def match_re(lines=None, res=None): - """ - """ - if not is_List(lines): - # CRs mess up matching (Windows) so split carefully - lines = re.split('\r?\n', lines) - if not is_List(res): - res = res.split("\n") - if len(lines) != len(res): - print("match_re: expected %d lines, found %d" % (len(res), len(lines))) - return - for i in range(len(lines)): - s = "^" + res[i] + "$" - try: - expr = re.compile(s) - except re.error as e: - msg = "Regular expression error in %s: %s" - raise re.error(msg % (repr(s), e.args[0])) - if not expr.search(lines[i]): - print("match_re: mismatch at line %d:\n search re='%s'\n line='%s'" % ( - i, s, lines[i])) - return - return 1 - - -def match_re_dotall(lines=None, res=None): - """ - """ - if not isinstance(lines, str): - lines = "\n".join(lines) - if not isinstance(res, str): - res = "\n".join(res) - s = "^" + res + "$" - try: - expr = re.compile(s, re.DOTALL) - except re.error as e: - msg = "Regular expression error in %s: %s" - raise re.error(msg % (repr(s), e.args[0])) - return expr.match(lines) - - -def simple_diff(a, b, fromfile='', tofile='', - fromfiledate='', tofiledate='', n=3, lineterm='\n'): - """ - A function with the same calling signature as difflib.context_diff - (diff -c) and difflib.unified_diff (diff -u) but which prints - output like the simple, unadorned 'diff" command. - """ - a = [to_str(q) for q in a] - b = [to_str(q) for q in b] - sm = difflib.SequenceMatcher(None, a, b) - - def comma(x1, x2): - return x1 + 1 == x2 and str(x2) or '%s,%s' % (x1 + 1, x2) - result = [] - for op, a1, a2, b1, b2 in sm.get_opcodes(): - if op == 'delete': - result.append("%sd%d" % (comma(a1, a2), b1)) - result.extend(['< ' + l for l in a[a1:a2]]) - elif op == 'insert': - result.append("%da%s" % (a1, comma(b1, b2))) - result.extend(['> ' + l for l in b[b1:b2]]) - elif op == 'replace': - result.append("%sc%s" % (comma(a1, a2), comma(b1, b2))) - result.extend(['< ' + l for l in a[a1:a2]]) - result.append('---') - result.extend(['> ' + l for l in b[b1:b2]]) - return result - - -def diff_re(a, b, fromfile='', tofile='', - fromfiledate='', tofiledate='', n=3, lineterm='\n'): - """ - A simple "diff" of two sets of lines when the expected lines - are regular expressions. This is a really dumb thing that - just compares each line in turn, so it doesn't look for - chunks of matching lines and the like--but at least it lets - you know exactly which line first didn't compare correctl... - """ - result = [] - diff = len(a) - len(b) - if diff < 0: - a = a + [''] * (-diff) - elif diff > 0: - b = b + [''] * diff - i = 0 - for aline, bline in zip(a, b): - s = "^" + aline + "$" - try: - expr = re.compile(s) - except re.error as e: - msg = "Regular expression error in %s: %s" - raise re.error(msg % (repr(s), e.args[0])) - if not expr.search(bline): - result.append("%sc%s" % (i + 1, i + 1)) - result.append('< ' + repr(a[i])) - result.append('---') - result.append('> ' + repr(b[i])) - i = i + 1 - return result - - -if os.name == 'posix': - def escape(arg): - "escape shell special characters" - slash = '\\' - special = '"$' - arg = arg.replace(slash, slash + slash) - for c in special: - arg = arg.replace(c, slash + c) - if re_space.search(arg): - arg = '"' + arg + '"' - return arg -else: - # Windows does not allow special characters in file names - # anyway, so no need for an escape function, we will just quote - # the arg. - def escape(arg): - if re_space.search(arg): - arg = '"' + arg + '"' - return arg - -if os.name == 'java': - python = os.path.join(sys.prefix, 'jython') -else: - python = os.environ.get('python_executable', sys.executable) -_python_ = escape(python) - -if sys.platform == 'win32': - - default_sleep_seconds = 2 - - def where_is(file, path=None, pathext=None): - if path is None: - path = os.environ['PATH'] - if is_String(path): - path = path.split(os.pathsep) - if pathext is None: - pathext = os.environ['PATHEXT'] - if is_String(pathext): - pathext = pathext.split(os.pathsep) - for ext in pathext: - if ext.lower() == file[-len(ext):].lower(): - pathext = [''] - break - for dir in path: - f = os.path.join(dir, file) - for ext in pathext: - fext = f + ext - if os.path.isfile(fext): - return fext - return None - -else: - - def where_is(file, path=None, pathext=None): - if path is None: - path = os.environ['PATH'] - if is_String(path): - path = path.split(os.pathsep) - for dir in path: - f = os.path.join(dir, file) - if os.path.isfile(f): - try: - st = os.stat(f) - except OSError: - continue - if stat.S_IMODE(st[stat.ST_MODE]) & 0o111: - return f - return None - - default_sleep_seconds = 1 - - -import subprocess - -try: - subprocess.Popen.terminate -except AttributeError: - if sys.platform == 'win32': - import win32process - - def terminate(self): - win32process.TerminateProcess(self._handle, 1) - else: - def terminate(self): - os.kill(self.pid, signal.SIGTERM) - method = types.MethodType(terminate, None, subprocess.Popen) - setattr(subprocess.Popen, 'terminate', method) - - -# From Josiah Carlson, -# ASPN : Python Cookbook : Module to allow Asynchronous subprocess use on Windows and Posix platforms -# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554 - -PIPE = subprocess.PIPE - -if sys.platform == 'win32': # and subprocess.mswindows: - try: - from win32file import ReadFile, WriteFile - from win32pipe import PeekNamedPipe - except ImportError: - # If PyWin32 is not available, try ctypes instead - # XXX These replicate _just_enough_ PyWin32 behaviour for our purposes - import ctypes - from ctypes.wintypes import DWORD - - def ReadFile(hFile, bufSize, ol=None): - assert ol is None - lpBuffer = ctypes.create_string_buffer(bufSize) - bytesRead = DWORD() - bErr = ctypes.windll.kernel32.ReadFile( - hFile, lpBuffer, bufSize, ctypes.byref(bytesRead), ol) - if not bErr: - raise ctypes.WinError() - return (0, ctypes.string_at(lpBuffer, bytesRead.value)) - - def WriteFile(hFile, data, ol=None): - assert ol is None - bytesWritten = DWORD() - bErr = ctypes.windll.kernel32.WriteFile( - hFile, data, len(data), ctypes.byref(bytesWritten), ol) - if not bErr: - raise ctypes.WinError() - return (0, bytesWritten.value) - - def PeekNamedPipe(hPipe, size): - assert size == 0 - bytesAvail = DWORD() - bErr = ctypes.windll.kernel32.PeekNamedPipe( - hPipe, None, size, None, ctypes.byref(bytesAvail), None) - if not bErr: - raise ctypes.WinError() - return ("", bytesAvail.value, None) - import msvcrt -else: - import select - import fcntl - - try: - fcntl.F_GETFL - except AttributeError: - fcntl.F_GETFL = 3 - - try: - fcntl.F_SETFL - except AttributeError: - fcntl.F_SETFL = 4 - - -class Popen(subprocess.Popen): - def recv(self, maxsize=None): - return self._recv('stdout', maxsize) - - def recv_err(self, maxsize=None): - return self._recv('stderr', maxsize) - - def send_recv(self, input='', maxsize=None): - return self.send(input), self.recv(maxsize), self.recv_err(maxsize) - - def get_conn_maxsize(self, which, maxsize): - if maxsize is None: - maxsize = 1024 - elif maxsize < 1: - maxsize = 1 - return getattr(self, which), maxsize - - def _close(self, which): - getattr(self, which).close() - setattr(self, which, None) - - if sys.platform == 'win32': # and subprocess.mswindows: - def send(self, input): - input = to_bytes(input) - if not self.stdin: - return None - - try: - x = msvcrt.get_osfhandle(self.stdin.fileno()) - (errCode, written) = WriteFile(x, input) - except ValueError: - return self._close('stdin') - except (subprocess.pywintypes.error, Exception) as why: - if why.args[0] in (109, errno.ESHUTDOWN): - return self._close('stdin') - raise - - return written - - def _recv(self, which, maxsize): - conn, maxsize = self.get_conn_maxsize(which, maxsize) - if conn is None: - return None - - try: - x = msvcrt.get_osfhandle(conn.fileno()) - (read, nAvail, nMessage) = PeekNamedPipe(x, 0) - if maxsize < nAvail: - nAvail = maxsize - if nAvail > 0: - (errCode, read) = ReadFile(x, nAvail, None) - except ValueError: - return self._close(which) - except (subprocess.pywintypes.error, Exception) as why: - if why.args[0] in (109, errno.ESHUTDOWN): - return self._close(which) - raise - - # if self.universal_newlines: - # read = self._translate_newlines(read) - return read - - else: - def send(self, input): - if not self.stdin: - return None - - if not select.select([], [self.stdin], [], 0)[1]: - return 0 - - try: - written = os.write(self.stdin.fileno(), - bytearray(input, 'utf-8')) - except OSError as why: - if why.args[0] == errno.EPIPE: # broken pipe - return self._close('stdin') - raise - - return written - - def _recv(self, which, maxsize): - conn, maxsize = self.get_conn_maxsize(which, maxsize) - if conn is None: - return None - - try: - flags = fcntl.fcntl(conn, fcntl.F_GETFL) - except TypeError: - flags = None - else: - if not conn.closed: - fcntl.fcntl(conn, fcntl.F_SETFL, flags | os.O_NONBLOCK) - - try: - if not select.select([conn], [], [], 0)[0]: - return '' - - r = conn.read(maxsize) - if not r: - return self._close(which) - - # if self.universal_newlines: - # r = self._translate_newlines(r) - return r - finally: - if not conn.closed and not flags is None: - fcntl.fcntl(conn, fcntl.F_SETFL, flags) - - -disconnect_message = "Other end disconnected!" - - -def recv_some(p, t=.1, e=1, tr=5, stderr=0): - if tr < 1: - tr = 1 - x = time.time() + t - y = [] - r = '' - pr = p.recv - if stderr: - pr = p.recv_err - while time.time() < x or r: - r = pr() - if r is None: - if e: - raise Exception(disconnect_message) - else: - break - elif r: - y.append(r) - else: - time.sleep(max((x - time.time()) / tr, 0)) - return ''.join(y) - - -def send_all(p, data): - while len(data): - sent = p.send(data) - if sent is None: - raise Exception(disconnect_message) - data = memoryview(data)[sent:] - - -_Cleanup = [] - - -def _clean(): - global _Cleanup - cleanlist = [c for c in _Cleanup if c] - del _Cleanup[:] - cleanlist.reverse() - for test in cleanlist: - test.cleanup() - - -atexit.register(_clean) - - -class TestCmd(object): - """Class TestCmd - """ - - def __init__(self, description=None, - program=None, - interpreter=None, - workdir=None, - subdir=None, - verbose=None, - match=None, - match_stdout=None, - match_stderr=None, - diff=None, - diff_stdout=None, - diff_stderr=None, - combine=0, - universal_newlines=True, - timeout=None): - self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) - self._cwd = os.getcwd() - self.description_set(description) - self.program_set(program) - self.interpreter_set(interpreter) - if verbose is None: - try: - verbose = max(0, int(os.environ.get('TESTCMD_VERBOSE', 0))) - except ValueError: - verbose = 0 - self.verbose_set(verbose) - self.combine = combine - self.universal_newlines = universal_newlines - self.process = None - self.set_timeout(timeout) - self.set_match_function(match, match_stdout, match_stderr) - self.set_diff_function(diff, diff_stdout, diff_stderr) - self._dirlist = [] - self._preserve = {'pass_test': 0, 'fail_test': 0, 'no_result': 0} - preserve_value = os.environ.get('PRESERVE', False) - if preserve_value not in [0, '0', 'False']: - self._preserve['pass_test'] = os.environ['PRESERVE'] - self._preserve['fail_test'] = os.environ['PRESERVE'] - self._preserve['no_result'] = os.environ['PRESERVE'] - else: - try: - self._preserve['pass_test'] = os.environ['PRESERVE_PASS'] - except KeyError: - pass - try: - self._preserve['fail_test'] = os.environ['PRESERVE_FAIL'] - except KeyError: - pass - try: - self._preserve['no_result'] = os.environ['PRESERVE_NO_RESULT'] - except KeyError: - pass - self._stdout = [] - self._stderr = [] - self.status = None - self.condition = 'no_result' - self.workdir_set(workdir) - self.subdir(subdir) - self.fixture_dirs = [] - - def __del__(self): - self.cleanup() - - def __repr__(self): - return "%x" % id(self) - - banner_char = '=' - banner_width = 80 - - def banner(self, s, width=None): - if width is None: - width = self.banner_width - return s + self.banner_char * (width - len(s)) - - escape = staticmethod(escape) - - def canonicalize(self, path): - if is_List(path): - path = os.path.join(*tuple(path)) - if not os.path.isabs(path): - path = os.path.join(self.workdir, path) - return path - - def chmod(self, path, mode): - """Changes permissions on the specified file or directory - path name.""" - path = self.canonicalize(path) - os.chmod(path, mode) - - def cleanup(self, condition=None): - """Removes any temporary working directories for the specified - TestCmd environment. If the environment variable PRESERVE was - set when the TestCmd environment was created, temporary working - directories are not removed. If any of the environment variables - PRESERVE_PASS, PRESERVE_FAIL, or PRESERVE_NO_RESULT were set - when the TestCmd environment was created, then temporary working - directories are not removed if the test passed, failed, or had - no result, respectively. Temporary working directories are also - preserved for conditions specified via the preserve method. - - Typically, this method is not called directly, but is used when - the script exits to clean up temporary working directories as - appropriate for the exit status. - """ - if not self._dirlist: - return - os.chdir(self._cwd) - self.workdir = None - if condition is None: - condition = self.condition - if self._preserve[condition]: - for dir in self._dirlist: - print(u"Preserved directory " + dir + "\n") - else: - list = self._dirlist[:] - list.reverse() - for dir in list: - self.writable(dir, 1) - shutil.rmtree(dir, ignore_errors=1) - self._dirlist = [] - - global _Cleanup - if self in _Cleanup: - _Cleanup.remove(self) - - def command_args(self, program=None, - interpreter=None, - arguments=None): - if not self.external: - if program: - if isinstance(program, str) and not os.path.isabs(program): - program = os.path.join(self._cwd, program) - else: - program = self.program - if not interpreter: - interpreter = self.interpreter - else: - if not program: - program = self.program - if not interpreter: - interpreter = self.interpreter - if not isinstance(program, (list, tuple)): - program = [program] - cmd = list(program) - if interpreter: - if not isinstance(interpreter, (list, tuple)): - interpreter = [interpreter] - cmd = list(interpreter) + cmd - if arguments: - if isinstance(arguments, str): - arguments = arguments.split() - cmd.extend(arguments) - return cmd - - def description_set(self, description): - """Set the description of the functionality being tested. - """ - self.description = description - - def set_diff_function(self, diff=_Null, stdout=_Null, stderr=_Null): - """Sets the specified diff functions. - """ - if diff is not _Null: - self._diff_function = diff - if stdout is not _Null: - self._diff_stdout_function = stdout - if stderr is not _Null: - self._diff_stderr_function = stderr - - def diff(self, a, b, name=None, diff_function=None, *args, **kw): - if diff_function is None: - try: - diff_function = getattr(self, self._diff_function) - except TypeError: - diff_function = self._diff_function - if diff_function is None: - diff_function = self.simple_diff - if name is not None: - print(self.banner(name)) - args = (a.splitlines(), b.splitlines()) + args - for line in diff_function(*args, **kw): - print(line) - - def diff_stderr(self, a, b, *args, **kw): - """Compare actual and expected file contents. - """ - try: - diff_stderr_function = getattr(self, self._diff_stderr_function) - except TypeError: - diff_stderr_function = self._diff_stderr_function - return self.diff(a, b, diff_function=diff_stderr_function, *args, **kw) - - def diff_stdout(self, a, b, *args, **kw): - """Compare actual and expected file contents. - """ - try: - diff_stdout_function = getattr(self, self._diff_stdout_function) - except TypeError: - diff_stdout_function = self._diff_stdout_function - return self.diff(a, b, diff_function=diff_stdout_function, *args, **kw) - - simple_diff = staticmethod(simple_diff) - - diff_re = staticmethod(diff_re) - - context_diff = staticmethod(difflib.context_diff) - - unified_diff = staticmethod(difflib.unified_diff) - - def fail_test(self, condition=1, function=None, skip=0, message=None): - """Cause the test to fail. - """ - if not condition: - return - self.condition = 'fail_test' - fail_test(self=self, - condition=condition, - function=function, - skip=skip, - message=message) - - def interpreter_set(self, interpreter): - """Set the program to be used to interpret the program - under test as a script. - """ - self.interpreter = interpreter - - def set_match_function(self, match=_Null, stdout=_Null, stderr=_Null): - """Sets the specified match functions. - """ - if match is not _Null: - self._match_function = match - if stdout is not _Null: - self._match_stdout_function = stdout - if stderr is not _Null: - self._match_stderr_function = stderr - - def match(self, lines, matches): - """Compare actual and expected file contents. - """ - try: - match_function = getattr(self, self._match_function) - except TypeError: - match_function = self._match_function - if match_function is None: - # Default is regular expression matches. - match_function = self.match_re - return match_function(lines, matches) - - def match_stderr(self, lines, matches): - """Compare actual and expected file contents. - """ - try: - match_stderr_function = getattr(self, self._match_stderr_function) - except TypeError: - match_stderr_function = self._match_stderr_function - if match_stderr_function is None: - # Default is to use whatever match= is set to. - match_stderr_function = self.match - return match_stderr_function(lines, matches) - - def match_stdout(self, lines, matches): - """Compare actual and expected file contents. - """ - try: - match_stdout_function = getattr(self, self._match_stdout_function) - except TypeError: - match_stdout_function = self._match_stdout_function - if match_stdout_function is None: - # Default is to use whatever match= is set to. - match_stdout_function = self.match - return match_stdout_function(lines, matches) - - match_exact = staticmethod(match_exact) - - match_caseinsensitive = staticmethod(match_caseinsensitive) - - match_re = staticmethod(match_re) - - match_re_dotall = staticmethod(match_re_dotall) - - def no_result(self, condition=1, function=None, skip=0): - """Report that the test could not be run. - """ - if not condition: - return - self.condition = 'no_result' - no_result(self=self, - condition=condition, - function=function, - skip=skip) - - def pass_test(self, condition=1, function=None): - """Cause the test to pass. - """ - if not condition: - return - self.condition = 'pass_test' - pass_test(self=self, condition=condition, function=function) - - def preserve(self, *conditions): - """Arrange for the temporary working directories for the - specified TestCmd environment to be preserved for one or more - conditions. If no conditions are specified, arranges for - the temporary working directories to be preserved for all - conditions. - """ - if conditions is (): - conditions = ('pass_test', 'fail_test', 'no_result') - for cond in conditions: - self._preserve[cond] = 1 - - def program_set(self, program): - """Set the executable program or script to be tested. - """ - if not self.external: - if program and not os.path.isabs(program): - program = os.path.join(self._cwd, program) - self.program = program - - def read(self, file, mode='rb', newline=None): - """Reads and returns the contents of the specified file name. - The file name may be a list, in which case the elements are - concatenated with the os.path.join() method. The file is - assumed to be under the temporary working directory unless it - is an absolute path name. The I/O mode for the file may - be specified; it must begin with an 'r'. The default is - 'rb' (binary read). - """ - file = self.canonicalize(file) - if mode[0] != 'r': - raise ValueError("mode must begin with 'r'") - if IS_PY3 and 'b' not in mode: - return open(file, mode, newline=newline).read() - else: - return open(file, mode).read() - - def rmdir(self, dir): - """Removes the specified dir name. - The dir name may be a list, in which case the elements are - concatenated with the os.path.join() method. The dir is - assumed to be under the temporary working directory unless it - is an absolute path name. - The dir must be empty. - """ - dir = self.canonicalize(dir) - os.rmdir(dir) - - def _timeout(self): - self.process.terminate() - self.timer.cancel() - self.timer = None - - def set_timeout(self, timeout): - self.timeout = timeout - self.timer = None - - def parse_path(self, path, suppress_current=False): - """Return a list with the single path components of path. - """ - head, tail = os.path.split(path) - result = [] - if not tail: - if head == path: - return [head] - else: - result.append(tail) - head, tail = os.path.split(head) - while head and tail: - result.append(tail) - head, tail = os.path.split(head) - result.append(head or tail) - result.reverse() - - return result - - def dir_fixture(self, srcdir, dstdir=None): - """Copies the contents of the specified folder srcdir from - the directory of the called script, to the current - working directory. - The srcdir name may be a list, in which case the elements are - concatenated with the os.path.join() method. The dstdir is - assumed to be under the temporary working directory, it gets - created automatically, if it does not already exist. - """ - - if srcdir and self.fixture_dirs and not os.path.isabs(srcdir): - for dir in self.fixture_dirs: - spath = os.path.join(dir, srcdir) - if os.path.isdir(spath): - break - else: - spath = srcdir - - if dstdir: - dstdir = self.canonicalize(dstdir) - else: - dstdir = '.' - - if dstdir != '.' and not os.path.exists(dstdir): - dstlist = self.parse_path(dstdir) - if len(dstlist) > 0 and dstlist[0] == ".": - dstlist = dstlist[1:] - for idx in range(len(dstlist)): - self.subdir(dstlist[:idx + 1]) - - if dstdir and self.workdir: - dstdir = os.path.join(self.workdir, dstdir) - - for entry in os.listdir(spath): - epath = os.path.join(spath, entry) - dpath = os.path.join(dstdir, entry) - if os.path.isdir(epath): - # Copy the subfolder - shutil.copytree(epath, dpath) - else: - shutil.copy(epath, dpath) - - def file_fixture(self, srcfile, dstfile=None): - """Copies the file srcfile from the directory of - the called script, to the current working directory. - The dstfile is assumed to be under the temporary working - directory unless it is an absolute path name. - If dstfile is specified its target directory gets created - automatically, if it does not already exist. - """ - srcpath, srctail = os.path.split(srcfile) - - if srcpath and (not self.fixture_dirs or os.path.isabs(srcpath)): - spath = srcfile - else: - for dir in self.fixture_dirs: - spath = os.path.join(dir, srcfile) - if os.path.isfile(spath): - break - - if not dstfile: - if srctail: - dpath = os.path.join(self.workdir, srctail) - else: - return - else: - dstpath, dsttail = os.path.split(dstfile) - if dstpath: - if not os.path.exists(os.path.join(self.workdir, dstpath)): - dstlist = self.parse_path(dstpath) - if len(dstlist) > 0 and dstlist[0] == ".": - dstlist = dstlist[1:] - for idx in range(len(dstlist)): - self.subdir(dstlist[:idx + 1]) - - dpath = os.path.join(self.workdir, dstfile) - shutil.copy(spath, dpath) - - def start(self, program=None, - interpreter=None, - arguments=None, - universal_newlines=None, - timeout=_Null, - **kw): - """ - Starts a program or script for the test environment. - - The specified program will have the original directory - prepended unless it is enclosed in a [list]. - """ - cmd = self.command_args(program, interpreter, arguments) - if self.verbose: - cmd_string = ' '.join([self.escape(c) for c in cmd]) - sys.stderr.write(cmd_string + "\n") - if universal_newlines is None: - universal_newlines = self.universal_newlines - - # On Windows, if we make stdin a pipe when we plan to send - # no input, and the test program exits before - # Popen calls msvcrt.open_osfhandle, that call will fail. - # So don't use a pipe for stdin if we don't need one. - stdin = kw.get('stdin', None) - if stdin is not None: - stdin = subprocess.PIPE - - combine = kw.get('combine', self.combine) - if combine: - stderr_value = subprocess.STDOUT - else: - stderr_value = subprocess.PIPE - - if timeout is _Null: - timeout = self.timeout - if timeout: - self.timer = threading.Timer(float(timeout), self._timeout) - self.timer.start() - - if IS_PY3 and sys.platform == 'win32': - # Set this otherwist stdout/stderr pipes default to - # windows default locale cp1252 which will throw exception - # if using non-ascii characters. - # For example test/Install/non-ascii-name.py - os.environ['PYTHONIOENCODING'] = 'utf-8' - - # It seems that all pythons up to py3.6 still set text mode if you set encoding. - # TODO: File enhancement request on python to propagate universal_newlines even - # if encoding is set.hg c - p = Popen(cmd, - stdin=stdin, - stdout=subprocess.PIPE, - stderr=stderr_value, - env=os.environ, - universal_newlines=False) - - self.process = p - return p - - @staticmethod - def fix_binary_stream(stream): - """ - Handle stdout/stderr from popen when we specify universal_newlines = False. - This will read from the pipes in binary mode, not decode the output, - and not convert line endings to \n. - We do this because in py3 (3.5) with universal_newlines=True, it will - choose the default system locale to decode the output, and this breaks unicode - output. Specifically breaking test/option--tree.py which outputs a unicode char. - - py 3.6 allows us to pass an encoding param to popen thus not requiring the decode - nor end of line handling, because we propagate universal_newlines as specified. - - TODO: Do we need to pass universal newlines into this function? - """ - - if not stream: - return stream - # TODO: Run full tests on both platforms and see if this fixes failures - # It seems that py3.6 still sets text mode if you set encoding. - elif sys.version_info[0] == 3:# TODO and sys.version_info[1] < 6: - stream = stream.decode('utf-8') - stream = stream.replace('\r\n', '\n') - elif sys.version_info[0] == 2: - stream = stream.replace('\r\n', '\n') - - return stream - - - def finish(self, popen=None, **kw): - """ - Finishes and waits for the process being run under control of - the specified popen argument, recording the exit status, - standard output and error output. - """ - if popen is None: - popen = self.process - stdout, stderr = popen.communicate() - - stdout = self.fix_binary_stream(stdout) - stderr = self.fix_binary_stream(stderr) - - if self.timer: - self.timer.cancel() - self.timer = None - self.status = popen.returncode - self.process = None - self._stdout.append(stdout or '') - self._stderr.append(stderr or '') - - def run(self, program=None, - interpreter=None, - arguments=None, - chdir=None, - stdin=None, - universal_newlines=None, - timeout=_Null): - """Runs a test of the program or script for the test - environment. Standard output and error output are saved for - future retrieval via the stdout() and stderr() methods. - - The specified program will have the original directory - prepended unless it is enclosed in a [list]. - """ - if self.external: - if not program: - program = self.program - if not interpreter: - interpreter = self.interpreter - - if universal_newlines is None: - universal_newlines = self.universal_newlines - - if chdir: - oldcwd = os.getcwd() - if not os.path.isabs(chdir): - chdir = os.path.join(self.workpath(chdir)) - if self.verbose: - sys.stderr.write("chdir(" + chdir + ")\n") - os.chdir(chdir) - p = self.start(program=program, - interpreter=interpreter, - arguments=arguments, - universal_newlines=universal_newlines, - timeout=timeout, - stdin=stdin) - if is_List(stdin): - stdin = ''.join(stdin) - - if stdin and IS_PY3:# and sys.version_info[1] < 6: - stdin = to_bytes(stdin) - - # TODO(sgk): figure out how to re-use the logic in the .finish() - # method above. Just calling it from here causes problems with - # subclasses that redefine .finish(). We could abstract this - # into Yet Another common method called both here and by .finish(), - # but that seems ill-thought-out. - stdout, stderr = p.communicate(input=stdin) - if self.timer: - self.timer.cancel() - self.timer = None - self.status = p.returncode - self.process = None - - stdout = self.fix_binary_stream(stdout) - stderr = self.fix_binary_stream(stderr) - - - self._stdout.append(stdout or '') - self._stderr.append(stderr or '') - - if chdir: - os.chdir(oldcwd) - if self.verbose >= 2: - write = sys.stdout.write - write('============ STATUS: %d\n' % self.status) - out = self.stdout() - if out or self.verbose >= 3: - write('============ BEGIN STDOUT (len=%d):\n' % len(out)) - write(out) - write('============ END STDOUT\n') - err = self.stderr() - if err or self.verbose >= 3: - write('============ BEGIN STDERR (len=%d)\n' % len(err)) - write(err) - write('============ END STDERR\n') - - def sleep(self, seconds=default_sleep_seconds): - """Sleeps at least the specified number of seconds. If no - number is specified, sleeps at least the minimum number of - seconds necessary to advance file time stamps on the current - system. Sleeping more seconds is all right. - """ - time.sleep(seconds) - - def stderr(self, run=None): - """Returns the error output from the specified run number. - If there is no specified run number, then returns the error - output of the last run. If the run number is less than zero, - then returns the error output from that many runs back from the - current run. - """ - if not run: - run = len(self._stderr) - elif run < 0: - run = len(self._stderr) + run - run = run - 1 - return self._stderr[run] - - def stdout(self, run=None): - """Returns the standard output from the specified run number. - If there is no specified run number, then returns the standard - output of the last run. If the run number is less than zero, - then returns the standard output from that many runs back from - the current run. - """ - if not run: - run = len(self._stdout) - elif run < 0: - run = len(self._stdout) + run - run = run - 1 - return self._stdout[run] - - def subdir(self, *subdirs): - """Create new subdirectories under the temporary working - directory, one for each argument. An argument may be a list, - in which case the list elements are concatenated using the - os.path.join() method. Subdirectories multiple levels deep - must be created using a separate argument for each level: - - test.subdir('sub', ['sub', 'dir'], ['sub', 'dir', 'ectory']) - - Returns the number of subdirectories actually created. - """ - count = 0 - for sub in subdirs: - if sub is None: - continue - if is_List(sub): - sub = os.path.join(*tuple(sub)) - new = os.path.join(self.workdir, sub) - try: - os.mkdir(new) - except OSError: - pass - else: - count = count + 1 - return count - - def symlink(self, target, link): - """Creates a symlink to the specified target. - The link name may be a list, in which case the elements are - concatenated with the os.path.join() method. The link is - assumed to be under the temporary working directory unless it - is an absolute path name. The target is *not* assumed to be - under the temporary working directory. - """ - if sys.platform == 'win32': - # Skip this on windows as we're not enabling it due to - # it requiring user permissions which aren't always present - # and we don't have a good way to detect those permissions yet. - return - link = self.canonicalize(link) - try: - os.symlink(target, link) - except AttributeError: - pass # Windows has no symlink - - def tempdir(self, path=None): - """Creates a temporary directory. - A unique directory name is generated if no path name is specified. - The directory is created, and will be removed when the TestCmd - object is destroyed. - """ - if path is None: - try: - path = tempfile.mktemp(prefix=tempfile.template) - except TypeError: - path = tempfile.mktemp() - os.mkdir(path) - - # Symlinks in the path will report things - # differently from os.getcwd(), so chdir there - # and back to fetch the canonical path. - cwd = os.getcwd() - try: - os.chdir(path) - path = os.getcwd() - finally: - os.chdir(cwd) - - # Uppercase the drive letter since the case of drive - # letters is pretty much random on win32: - drive, rest = os.path.splitdrive(path) - if drive: - path = drive.upper() + rest - - # - self._dirlist.append(path) - - global _Cleanup - if self not in _Cleanup: - _Cleanup.append(self) - - return path - - def touch(self, path, mtime=None): - """Updates the modification time on the specified file or - directory path name. The default is to update to the - current time if no explicit modification time is specified. - """ - path = self.canonicalize(path) - atime = os.path.getatime(path) - if mtime is None: - mtime = time.time() - os.utime(path, (atime, mtime)) - - def unlink(self, file): - """Unlinks the specified file name. - The file name may be a list, in which case the elements are - concatenated with the os.path.join() method. The file is - assumed to be under the temporary working directory unless it - is an absolute path name. - """ - file = self.canonicalize(file) - os.unlink(file) - - def verbose_set(self, verbose): - """Set the verbose level. - """ - self.verbose = verbose - - def where_is(self, file, path=None, pathext=None): - """Find an executable file. - """ - if is_List(file): - file = os.path.join(*tuple(file)) - if not os.path.isabs(file): - file = where_is(file, path, pathext) - return file - - def workdir_set(self, path): - """Creates a temporary working directory with the specified - path name. If the path is a null string (''), a unique - directory name is created. - """ - if (path != None): - if path == '': - path = None - path = self.tempdir(path) - self.workdir = path - - def workpath(self, *args): - """Returns the absolute path name to a subdirectory or file - within the current temporary working directory. Concatenates - the temporary working directory name with the specified - arguments using the os.path.join() method. - """ - return os.path.join(self.workdir, *tuple(args)) - - def readable(self, top, read=1): - """Make the specified directory tree readable (read == 1) - or not (read == None). - - This method has no effect on Windows systems, which use a - completely different mechanism to control file readability. - """ - - if sys.platform == 'win32': - return - - if read: - def do_chmod(fname): - try: - st = os.stat(fname) - except OSError: - pass - else: - os.chmod(fname, stat.S_IMODE( - st[stat.ST_MODE] | stat.S_IREAD)) - else: - def do_chmod(fname): - try: - st = os.stat(fname) - except OSError: - pass - else: - os.chmod(fname, stat.S_IMODE( - st[stat.ST_MODE] & ~stat.S_IREAD)) - - if os.path.isfile(top): - # If it's a file, that's easy, just chmod it. - do_chmod(top) - elif read: - # It's a directory and we're trying to turn on read - # permission, so it's also pretty easy, just chmod the - # directory and then chmod every entry on our walk down the - # tree. - do_chmod(top) - for dirpath, dirnames, filenames in os.walk(top): - for name in dirnames + filenames: - do_chmod(os.path.join(dirpath, name)) - else: - # It's a directory and we're trying to turn off read - # permission, which means we have to chmod the directories - # in the tree bottom-up, lest disabling read permission from - # the top down get in the way of being able to get at lower - # parts of the tree. - for dirpath, dirnames, filenames in os.walk(top, topdown=0): - for name in dirnames + filenames: - do_chmod(os.path.join(dirpath, name)) - do_chmod(top) - - def writable(self, top, write=1): - """Make the specified directory tree writable (write == 1) - or not (write == None). - """ - - if sys.platform == 'win32': - - if write: - def do_chmod(fname): - try: - os.chmod(fname, stat.S_IWRITE) - except OSError: - pass - else: - def do_chmod(fname): - try: - os.chmod(fname, stat.S_IREAD) - except OSError: - pass - - else: - - if write: - def do_chmod(fname): - try: - st = os.stat(fname) - except OSError: - pass - else: - os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE] | 0o200)) - else: - def do_chmod(fname): - try: - st = os.stat(fname) - except OSError: - pass - else: - os.chmod(fname, stat.S_IMODE( - st[stat.ST_MODE] & ~0o200)) - - if os.path.isfile(top): - do_chmod(top) - else: - do_chmod(top) - for dirpath, dirnames, filenames in os.walk(top, topdown=0): - for name in dirnames + filenames: - do_chmod(os.path.join(dirpath, name)) - - def executable(self, top, execute=1): - """Make the specified directory tree executable (execute == 1) - or not (execute == None). - - This method has no effect on Windows systems, which use a - completely different mechanism to control file executability. - """ - - if sys.platform == 'win32': - return - - if execute: - def do_chmod(fname): - try: - st = os.stat(fname) - except OSError: - pass - else: - os.chmod(fname, stat.S_IMODE( - st[stat.ST_MODE] | stat.S_IEXEC)) - else: - def do_chmod(fname): - try: - st = os.stat(fname) - except OSError: - pass - else: - os.chmod(fname, stat.S_IMODE( - st[stat.ST_MODE] & ~stat.S_IEXEC)) - - if os.path.isfile(top): - # If it's a file, that's easy, just chmod it. - do_chmod(top) - elif execute: - # It's a directory and we're trying to turn on execute - # permission, so it's also pretty easy, just chmod the - # directory and then chmod every entry on our walk down the - # tree. - do_chmod(top) - for dirpath, dirnames, filenames in os.walk(top): - for name in dirnames + filenames: - do_chmod(os.path.join(dirpath, name)) - else: - # It's a directory and we're trying to turn off execute - # permission, which means we have to chmod the directories - # in the tree bottom-up, lest disabling execute permission from - # the top down get in the way of being able to get at lower - # parts of the tree. - for dirpath, dirnames, filenames in os.walk(top, topdown=0): - for name in dirnames + filenames: - do_chmod(os.path.join(dirpath, name)) - do_chmod(top) - - def write(self, file, content, mode='wb'): - """Writes the specified content text (second argument) to the - specified file name (first argument). The file name may be - a list, in which case the elements are concatenated with the - os.path.join() method. The file is created under the temporary - working directory. Any subdirectories in the path must already - exist. The I/O mode for the file may be specified; it must - begin with a 'w'. The default is 'wb' (binary write). - """ - file = self.canonicalize(file) - if mode[0] != 'w': - raise ValueError("mode must begin with 'w'") - with open(file, mode) as f: - try: - f.write(content) - except TypeError as e: - # python 3 default strings are not bytes, but unicode - f.write(bytes(content, 'utf-8')) - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/QMTest/TestCmdTests.py b/QMTest/TestCmdTests.py deleted file mode 100644 index b9226fd..0000000 --- a/QMTest/TestCmdTests.py +++ /dev/null @@ -1,3419 +0,0 @@ -#!/usr/bin/env python -""" -TestCmdTests.py: Unit tests for the TestCmd.py module. - -Copyright 2000-2010 Steven Knight -This module is free software, and you may redistribute it and/or modify -it under the same terms as Python itself, so long as this copyright message -and disclaimer are retained in their original form. - -IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -""" - -__author__ = "Steven Knight " -__revision__ = "TestCmdTests.py 1.3.D001 2010/06/03 12:58:27 knight" - -import os -import shutil -import signal -import stat -from StringIO import StringIO -import sys -import tempfile -import time -import types -import unittest -from UserList import UserList - - -# Strip the current directory so we get the right TestCmd.py module. -sys.path = sys.path[1:] - -import TestCmd - -def _is_readable(path): - # XXX this doesn't take into account UID, it assumes it's our file - return os.stat(path)[stat.ST_MODE] & stat.S_IREAD - -def _is_writable(path): - # XXX this doesn't take into account UID, it assumes it's our file - return os.stat(path)[stat.ST_MODE] & stat.S_IWRITE - -def _is_executable(path): - # XXX this doesn't take into account UID, it assumes it's our file - return os.stat(path)[stat.ST_MODE] & stat.S_IEXEC - -def _clear_dict(dict, *keys): - for key in keys: - try: - dict[key] = '' # del dict[key] - except KeyError: - pass - -import subprocess - -try: - subprocess.Popen.terminate -except AttributeError: - if sys.platform == 'win32': - import win32process - def terminate(self): - win32process.TerminateProcess(self._handle, 1) - else: - def terminate(self): - os.kill(self.pid, signal.SIGTERM) - method = types.MethodType(terminate, None, subprocess.Popen) - setattr(subprocess.Popen, 'terminate', method) - -class ExitError(Exception): - pass - -class TestCmdTestCase(unittest.TestCase): - """Base class for TestCmd test cases, with fixture and utility methods.""" - - def setUp(self): - self.orig_cwd = os.getcwd() - - def tearDown(self): - os.chdir(self.orig_cwd) - - def setup_run_scripts(self): - class T: - pass - - t = T() - - t.script = 'script' - t.scriptx = 'scriptx.bat' - t.script1 = 'script_1.txt' - t.scriptout = 'scriptout' - t.scripterr = 'scripterr' - fmt = "import os, sys; cwd = os.getcwd(); " + \ - "sys.stdout.write('%s: STDOUT: %%s: %%s\\n' %% (cwd, sys.argv[1:])); " + \ - "sys.stderr.write('%s: STDERR: %%s: %%s\\n' %% (cwd, sys.argv[1:]))" - fmtout = "import os, sys; cwd = os.getcwd(); " + \ - "sys.stdout.write('%s: STDOUT: %%s: %%s\\n' %% (cwd, sys.argv[1:]))" - fmterr = "import os, sys; cwd = os.getcwd(); " + \ - "sys.stderr.write('%s: STDERR: %%s: %%s\\n' %% (cwd, sys.argv[1:]))" - text = fmt % (t.script, t.script) - textx = fmt % (t.scriptx, t.scriptx) - if sys.platform == 'win32': - textx = textx.replace('%', '%%') - textx = '@python -c "%s"' % textx + ' %1 %2 %3 %4 %5 %6 %7 %8 %9\n' - else: - textx = '#! /usr/bin/env python\n' + textx + '\n' - text1 = 'A first line to be ignored!\n' + fmt % (t.script1, t.script1) - textout = fmtout % (t.scriptout) - texterr = fmterr % (t.scripterr) - - run_env = TestCmd.TestCmd(workdir = '') - run_env.subdir('sub dir') - t.run_env = run_env - - t.sub_dir = run_env.workpath('sub dir') - t.script_path = run_env.workpath('sub dir', t.script) - t.scriptx_path = run_env.workpath('sub dir', t.scriptx) - t.script1_path = run_env.workpath('sub dir', t.script1) - t.scriptout_path = run_env.workpath('sub dir', t.scriptout) - t.scripterr_path = run_env.workpath('sub dir', t.scripterr) - - run_env.write(t.script_path, text) - run_env.write(t.scriptx_path, textx) - run_env.write(t.script1_path, text1) - run_env.write(t.scriptout_path, textout) - run_env.write(t.scripterr_path, texterr) - - os.chmod(t.script_path, 0o644) # XXX UNIX-specific - os.chmod(t.scriptx_path, 0o755) # XXX UNIX-specific - os.chmod(t.script1_path, 0o644) # XXX UNIX-specific - os.chmod(t.scriptout_path, 0o644) # XXX UNIX-specific - os.chmod(t.scripterr_path, 0o644) # XXX UNIX-specific - - t.orig_cwd = os.getcwd() - - t.workdir = run_env.workpath('sub dir') - os.chdir(t.workdir) - - return t - - def translate_newlines(self, data): - data = data.replace("\r\n", "\n") - return data - - def call_python(self, input, python=None): - if python is None: - python = sys.executable - p = subprocess.Popen(python, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE) - stdout, stderr = p.communicate(input) - stdout = self.translate_newlines(stdout) - stderr = self.translate_newlines(stderr) - return stdout, stderr, p.returncode - - def popen_python(self, input, status=0, stdout="", stderr="", python=None): - if python is None: - python = sys.executable - _stdout, _stderr, _status = self.call_python(input, python) - _stdout = self.translate_newlines(_stdout) - _stderr = self.translate_newlines(_stderr) - assert _status == status, \ - "status = %s, expected %s\n" % (str(_status), str(status)) + \ - "STDOUT ===================\n" + _stdout + \ - "STDERR ===================\n" + _stderr - assert _stdout == stdout, \ - "Expected STDOUT ==========\n" + stdout + \ - "Actual STDOUT ============\n" + _stdout + \ - "STDERR ===================\n" + _stderr - assert _stderr == stderr, \ - "Expected STDERR ==========\n" + stderr + \ - "Actual STDERR ============\n" + _stderr - - def run_match(self, content, *args): - expect = "%s: %s: %s: %s\n" % args - content = self.translate_newlines(content) - assert content == expect, \ - "Expected %s ==========\n" % args[1] + expect + \ - "Actual %s ============\n" % args[1] + content - - - -class __init__TestCase(TestCmdTestCase): - def test_init(self): - """Test init()""" - test = TestCmd.TestCmd() - test = TestCmd.TestCmd(description = 'test') - test = TestCmd.TestCmd(description = 'test', program = 'foo') - test = TestCmd.TestCmd(description = 'test', - program = 'foo', - universal_newlines=None) - - - -class basename_TestCase(TestCmdTestCase): - def test_basename(self): - """Test basename() [XXX TO BE WRITTEN]""" - assert 1 == 1 - - - -class cleanup_TestCase(TestCmdTestCase): - def test_cleanup(self): - """Test cleanup()""" - test = TestCmd.TestCmd(workdir = '') - wdir = test.workdir - test.write('file1', "Test file #1\n") - test.cleanup() - assert not os.path.exists(wdir) - - def test_writable(self): - """Test cleanup() when the directory isn't writable""" - test = TestCmd.TestCmd(workdir = '') - wdir = test.workdir - test.write('file2', "Test file #2\n") - os.chmod(test.workpath('file2'), 0o400) - os.chmod(wdir, 0o500) - test.cleanup() - assert not os.path.exists(wdir) - - def test_shutil(self): - """Test cleanup() when used with shutil""" - test = TestCmd.TestCmd(workdir = '') - wdir = test.workdir - os.chdir(wdir) - - import shutil - save_rmtree = shutil.rmtree - def my_rmtree(dir, ignore_errors=0, wdir=wdir, _rmtree=save_rmtree): - assert os.getcwd() != wdir - return _rmtree(dir, ignore_errors=ignore_errors) - try: - shutil.rmtree = my_rmtree - test.cleanup() - finally: - shutil.rmtree = save_rmtree - - def test_atexit(self): - """Test cleanup() when atexit is used""" - self.popen_python("""from __future__ import print_function -import sys -sys.path = ['%s'] + sys.path -import atexit -def my_exitfunc(): - print("my_exitfunc()") -atexit.register(my_exitfunc) -import TestCmd -result = TestCmd.TestCmd(workdir = '') -sys.exit(0) -""" % self.orig_cwd, stdout='my_exitfunc()\n') - - def test_exitfunc(self): - """Test cleanup() when sys.exitfunc is set""" - self.popen_python("""from __future__ import print_function -import sys -sys.path = ['%s'] + sys.path -def my_exitfunc(): - print("my_exitfunc()") -sys.exitfunc = my_exitfunc -import TestCmd -result = TestCmd.TestCmd(workdir = '') -sys.exit(0) -""" % self.orig_cwd, stdout='my_exitfunc()\n') - - - -class chmod_TestCase(TestCmdTestCase): - def test_chmod(self): - """Test chmod()""" - test = TestCmd.TestCmd(workdir = '', subdir = 'sub') - - wdir_file1 = os.path.join(test.workdir, 'file1') - wdir_sub_file2 = os.path.join(test.workdir, 'sub', 'file2') - - open(wdir_file1, 'w').write("") - open(wdir_sub_file2, 'w').write("") - - if sys.platform == 'win32': - - test.chmod(wdir_file1, stat.S_IREAD) - test.chmod(['sub', 'file2'], stat.S_IWRITE) - - file1_mode = stat.S_IMODE(os.stat(wdir_file1)[stat.ST_MODE]) - assert file1_mode == 0o444, '0%o' % file1_mode - file2_mode = stat.S_IMODE(os.stat(wdir_sub_file2)[stat.ST_MODE]) - assert file2_mode == 0o666, '0%o' % file2_mode - - test.chmod('file1', stat.S_IWRITE) - test.chmod(wdir_sub_file2, stat.S_IREAD) - - file1_mode = stat.S_IMODE(os.stat(wdir_file1)[stat.ST_MODE]) - assert file1_mode == 0o666, '0%o' % file1_mode - file2_mode = stat.S_IMODE(os.stat(wdir_sub_file2)[stat.ST_MODE]) - assert file2_mode == 0o444, '0%o' % file2_mode - - else: - - test.chmod(wdir_file1, 0o700) - test.chmod(['sub', 'file2'], 0o760) - - file1_mode = stat.S_IMODE(os.stat(wdir_file1)[stat.ST_MODE]) - assert file1_mode == 0o700, '0%o' % file1_mode - file2_mode = stat.S_IMODE(os.stat(wdir_sub_file2)[stat.ST_MODE]) - assert file2_mode == 0o760, '0%o' % file2_mode - - test.chmod('file1', 0o765) - test.chmod(wdir_sub_file2, 0o567) - - file1_mode = stat.S_IMODE(os.stat(wdir_file1)[stat.ST_MODE]) - assert file1_mode == 0o765, '0%o' % file1_mode - file2_mode = stat.S_IMODE(os.stat(wdir_sub_file2)[stat.ST_MODE]) - assert file2_mode == 0o567, '0%o' % file2_mode - - - -class combine_TestCase(TestCmdTestCase): - def test_combine(self): - """Test combining stdout and stderr""" - run_env = TestCmd.TestCmd(workdir = '') - run_env.write('run1', """import sys -sys.stdout.write("run1 STDOUT %s\\n" % sys.argv[1:]) -sys.stdout.write("run1 STDOUT second line\\n") -sys.stderr.write("run1 STDERR %s\\n" % sys.argv[1:]) -sys.stderr.write("run1 STDERR second line\\n") -sys.stdout.write("run1 STDOUT third line\\n") -sys.stderr.write("run1 STDERR third line\\n") -""") - run_env.write('run2', """import sys -sys.stdout.write("run2 STDOUT %s\\n" % sys.argv[1:]) -sys.stdout.write("run2 STDOUT second line\\n") -sys.stderr.write("run2 STDERR %s\\n" % sys.argv[1:]) -sys.stderr.write("run2 STDERR second line\\n") -sys.stdout.write("run2 STDOUT third line\\n") -sys.stderr.write("run2 STDERR third line\\n") -""") - cwd = os.getcwd() - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - try: - test = TestCmd.TestCmd(interpreter = 'python', - workdir = '', - combine = 1) - try: - output = test.stdout() - except IndexError: - pass - else: - raise IndexError("got unexpected output:\n\t`%s'\n" % output) - - # The underlying system subprocess implementations can combine - # stdout and stderr in different orders, so we accomodate both. - - test.program_set('run1') - test.run(arguments = 'foo bar') - stdout_lines = """\ -run1 STDOUT ['foo', 'bar'] -run1 STDOUT second line -run1 STDOUT third line -""" - stderr_lines = """\ -run1 STDERR ['foo', 'bar'] -run1 STDERR second line -run1 STDERR third line -""" - foo_bar_expect = (stdout_lines + stderr_lines, - stderr_lines + stdout_lines) - - test.program_set('run2') - test.run(arguments = 'snafu') - stdout_lines = """\ -run2 STDOUT ['snafu'] -run2 STDOUT second line -run2 STDOUT third line -""" - stderr_lines = """\ -run2 STDERR ['snafu'] -run2 STDERR second line -run2 STDERR third line -""" - snafu_expect = (stdout_lines + stderr_lines, - stderr_lines + stdout_lines) - - # XXX SHOULD TEST ABSOLUTE NUMBER AS WELL - output = test.stdout() - output = self.translate_newlines(output) - assert output in snafu_expect, output - error = test.stderr() - assert error == '', error - - output = test.stdout(run = -1) - output = self.translate_newlines(output) - assert output in foo_bar_expect, output - error = test.stderr(-1) - assert error == '', error - finally: - os.chdir(cwd) - - - -class description_TestCase(TestCmdTestCase): - def test_description(self): - """Test description()""" - test = TestCmd.TestCmd() - assert test.description is None, 'initialized description?' - test = TestCmd.TestCmd(description = 'test') - assert test.description == 'test', 'uninitialized description' - test.description_set('foo') - assert test.description == 'foo', 'did not set description' - - - -class diff_TestCase(TestCmdTestCase): - def test_diff_re(self): - """Test diff_re()""" - result = TestCmd.diff_re(["abcde"], ["abcde"]) - assert result == [], result - result = TestCmd.diff_re(["a.*e"], ["abcde"]) - assert result == [], result - result = TestCmd.diff_re(["a.*e"], ["xxx"]) - assert result == ['1c1', "< 'a.*e'", '---', "> 'xxx'"], result - - def test_diff_custom_function(self): - """Test diff() using a custom function""" - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -def my_diff(a, b): - return [ - '*****', - a, - '*****', - b, - '*****', - ] -test = TestCmd.TestCmd(diff = my_diff) -test.diff("a\\nb1\\nc\\n", "a\\nb2\\nc\\n", "STDOUT") -sys.exit(0) -""" % self.orig_cwd, - stdout = """\ -STDOUT========================================================================== -***** -['a', 'b1', 'c'] -***** -['a', 'b2', 'c'] -***** -""") - - def test_diff_string(self): - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(diff = 'diff_re') -test.diff("a\\nb1\\nc\\n", "a\\nb2\\nc\\n", 'STDOUT') -sys.exit(0) -""" % self.orig_cwd, - stdout = """\ -STDOUT========================================================================== -2c2 -< 'b1' ---- -> 'b2' -""") - - def test_error(self): - """Test handling a compilation error in TestCmd.diff_re()""" - script_input = """import sys -sys.path = ['%s'] + sys.path -import TestCmd -assert TestCmd.diff_re(["a.*(e"], ["abcde"]) -sys.exit(0) -""" % self.orig_cwd - stdout, stderr, status = self.call_python(script_input) - assert status == 1, status - expect1 = "Regular expression error in '^a.*(e$': missing )\n" - expect2 = "Regular expression error in '^a.*(e$': unbalanced parenthesis\n" - assert (stderr.find(expect1) != -1 or - stderr.find(expect2) != -1), repr(stderr) - - def test_simple_diff_static_method(self): - """Test calling the TestCmd.TestCmd.simple_diff() static method""" - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -result = TestCmd.TestCmd.simple_diff(['a', 'b', 'c', 'e', 'f1'], - ['a', 'c', 'd', 'e', 'f2']) -expect = ['2d1', '< b', '3a3', '> d', '5c5', '< f1', '---', '> f2'] -assert result == expect, result -sys.exit(0) -""" % self.orig_cwd) - - def test_context_diff_static_method(self): - """Test calling the TestCmd.TestCmd.context_diff() static method""" - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -result = TestCmd.TestCmd.context_diff(['a\\n', 'b\\n', 'c\\n', 'e\\n', 'f1\\n'], - ['a\\n', 'c\\n', 'd\\n', 'e\\n', 'f2\\n']) -result = list(result) -expect = [ - '*** \\n', - '--- \\n', - '***************\\n', - '*** 1,5 ****\\n', - ' a\\n', - '- b\\n', - ' c\\n', - ' e\\n', - '! f1\\n', - '--- 1,5 ----\\n', - ' a\\n', - ' c\\n', - '+ d\\n', - ' e\\n', - '! f2\\n', -] -assert result == expect, result -sys.exit(0) -""" % self.orig_cwd) - - def test_unified_diff_static_method(self): - """Test calling the TestCmd.TestCmd.unified_diff() static method""" - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -result = TestCmd.TestCmd.unified_diff(['a\\n', 'b\\n', 'c\\n', 'e\\n', 'f1\\n'], - ['a\\n', 'c\\n', 'd\\n', 'e\\n', 'f2\\n']) -result = list(result) -expect = [ - '--- \\n', - '+++ \\n', - '@@ -1,5 +1,5 @@\\n', - ' a\\n', - '-b\\n', - ' c\\n', - '+d\\n', - ' e\\n', - '-f1\\n', - '+f2\\n' -] -assert result == expect, result -sys.exit(0) -""" % self.orig_cwd) - - def test_diff_re_static_method(self): - """Test calling the TestCmd.TestCmd.diff_re() static method""" - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -result = TestCmd.TestCmd.diff_re(['a', 'b', 'c', '.', 'f1'], - ['a', 'c', 'd', 'e', 'f2']) -expect = [ - '2c2', - "< 'b'", - '---', - "> 'c'", - '3c3', - "< 'c'", - '---', - "> 'd'", - '5c5', - "< 'f1'", - '---', - "> 'f2'" -] -assert result == expect, result -sys.exit(0) -""" % self.orig_cwd) - - - -class diff_stderr_TestCase(TestCmdTestCase): - def test_diff_stderr_default(self): - """Test diff_stderr() default behavior""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd() -test.diff_stderr('a\nb1\nc\n', 'a\nb2\nc\n') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -2c2 -< b1 ---- -> b2 -""") - - def test_diff_stderr_not_affecting_diff_stdout(self): - """Test diff_stderr() not affecting diff_stdout() behavior""" - self.popen_python(r"""from __future__ import print_function -import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(diff_stderr='diff_re') -print("diff_stderr:") -test.diff_stderr('a\nb.\nc\n', 'a\nbb\nc\n') -print("diff_stdout:") -test.diff_stdout('a\nb.\nc\n', 'a\nbb\nc\n') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -diff_stderr: -diff_stdout: -2c2 -< b. ---- -> bb -""") - - def test_diff_stderr_custom_function(self): - """Test diff_stderr() using a custom function""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -def my_diff(a, b): - return ["a:"] + a + ["b:"] + b -test = TestCmd.TestCmd(diff_stderr=my_diff) -test.diff_stderr('abc', 'def') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -a: -abc -b: -def -""") - - def test_diff_stderr_TestCmd_function(self): - """Test diff_stderr() using a TestCmd function""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(diff_stderr = TestCmd.diff_re) -test.diff_stderr('a\n.\n', 'b\nc\n') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -1c1 -< 'a' ---- -> 'b' -""") - - def test_diff_stderr_static_method(self): - """Test diff_stderr() using a static method""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(diff_stderr=TestCmd.TestCmd.diff_re) -test.diff_stderr('a\n.\n', 'b\nc\n') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -1c1 -< 'a' ---- -> 'b' -""") - - def test_diff_stderr_string(self): - """Test diff_stderr() using a string to fetch the diff method""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(diff_stderr='diff_re') -test.diff_stderr('a\n.\n', 'b\nc\n') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -1c1 -< 'a' ---- -> 'b' -""") - - - -class diff_stdout_TestCase(TestCmdTestCase): - def test_diff_stdout_default(self): - """Test diff_stdout() default behavior""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd() -test.diff_stdout('a\nb1\nc\n', 'a\nb2\nc\n') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -2c2 -< b1 ---- -> b2 -""") - - def test_diff_stdout_not_affecting_diff_stderr(self): - """Test diff_stdout() not affecting diff_stderr() behavior""" - self.popen_python(r"""from __future__ import print_function -import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(diff_stdout='diff_re') -print("diff_stdout:") -test.diff_stdout('a\nb.\nc\n', 'a\nbb\nc\n') -print("diff_stderr:") -test.diff_stderr('a\nb.\nc\n', 'a\nbb\nc\n') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -diff_stdout: -diff_stderr: -2c2 -< b. ---- -> bb -""") - - def test_diff_stdout_custom_function(self): - """Test diff_stdout() using a custom function""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -def my_diff(a, b): - return ["a:"] + a + ["b:"] + b -test = TestCmd.TestCmd(diff_stdout=my_diff) -test.diff_stdout('abc', 'def') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -a: -abc -b: -def -""") - - def test_diff_stdout_TestCmd_function(self): - """Test diff_stdout() using a TestCmd function""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(diff_stdout = TestCmd.diff_re) -test.diff_stdout('a\n.\n', 'b\nc\n') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -1c1 -< 'a' ---- -> 'b' -""") - - def test_diff_stdout_static_method(self): - """Test diff_stdout() using a static method""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(diff_stdout=TestCmd.TestCmd.diff_re) -test.diff_stdout('a\n.\n', 'b\nc\n') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -1c1 -< 'a' ---- -> 'b' -""") - - def test_diff_stdout_string(self): - """Test diff_stdout() using a string to fetch the diff method""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(diff_stdout='diff_re') -test.diff_stdout('a\n.\n', 'b\nc\n') -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -1c1 -< 'a' ---- -> 'b' -""") - - - -class exit_TestCase(TestCmdTestCase): - def test_exit(self): - """Test exit()""" - def _test_it(cwd, tempdir, condition, preserved): - close_true = {'pass_test': 1, 'fail_test': 0, 'no_result': 0} - exit_status = {'pass_test': 0, 'fail_test': 1, 'no_result': 2} - result_string = {'pass_test': "PASSED\n", - 'fail_test': "FAILED test at line 5 of \n", - 'no_result': "NO RESULT for test at line 5 of \n"} - global ExitError - input = """import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(workdir = '%s') -test.%s() -""" % (cwd, tempdir, condition) - stdout, stderr, status = self.call_python(input, python="python") - if close_true[condition]: - unexpected = (status != 0) - else: - unexpected = (status == 0) - if unexpected: - msg = "Unexpected exit status from python: %s\n" - raise ExitError(msg % status + stdout + stderr) - if status != exit_status[condition]: - msg = "Expected exit status %d, got %d\n" - raise ExitError(msg % (exit_status[condition], status)) - if stderr != result_string[condition]: - msg = "Expected error output:\n%sGot error output:\n%s" - raise ExitError(msg % (result_string[condition], stderr)) - if preserved: - if not os.path.exists(tempdir): - msg = "Working directory %s was mistakenly removed\n" - raise ExitError(msg % tempdir + stdout) - else: - if os.path.exists(tempdir): - msg = "Working directory %s was mistakenly preserved\n" - raise ExitError(msg % tempdir + stdout) - - run_env = TestCmd.TestCmd(workdir = '') - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - try: - cwd = self.orig_cwd - _clear_dict(os.environ, 'PRESERVE', 'PRESERVE_PASS', 'PRESERVE_FAIL', 'PRESERVE_NO_RESULT') - _test_it(cwd, 'dir01', 'pass_test', 0) - _test_it(cwd, 'dir02', 'fail_test', 0) - _test_it(cwd, 'dir03', 'no_result', 0) - os.environ['PRESERVE'] = '1' - _test_it(cwd, 'dir04', 'pass_test', 1) - _test_it(cwd, 'dir05', 'fail_test', 1) - _test_it(cwd, 'dir06', 'no_result', 1) - os.environ['PRESERVE'] = '' # del os.environ['PRESERVE'] - os.environ['PRESERVE_PASS'] = '1' - _test_it(cwd, 'dir07', 'pass_test', 1) - _test_it(cwd, 'dir08', 'fail_test', 0) - _test_it(cwd, 'dir09', 'no_result', 0) - os.environ['PRESERVE_PASS'] = '' # del os.environ['PRESERVE_PASS'] - os.environ['PRESERVE_FAIL'] = '1' - _test_it(cwd, 'dir10', 'pass_test', 0) - _test_it(cwd, 'dir11', 'fail_test', 1) - _test_it(cwd, 'dir12', 'no_result', 0) - os.environ['PRESERVE_FAIL'] = '' # del os.environ['PRESERVE_FAIL'] - os.environ['PRESERVE_NO_RESULT'] = '1' - _test_it(cwd, 'dir13', 'pass_test', 0) - _test_it(cwd, 'dir14', 'fail_test', 0) - _test_it(cwd, 'dir15', 'no_result', 1) - os.environ['PRESERVE_NO_RESULT'] = '' # del os.environ['PRESERVE_NO_RESULT'] - finally: - _clear_dict(os.environ, 'PRESERVE', 'PRESERVE_PASS', 'PRESERVE_FAIL', 'PRESERVE_NO_RESULT') - - - -class fail_test_TestCase(TestCmdTestCase): - def test_fail_test(self): - """Test fail_test()""" - run_env = TestCmd.TestCmd(workdir = '') - run_env.write('run', """import sys -sys.stdout.write("run: STDOUT\\n") -sys.stderr.write("run: STDERR\\n") -""") - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -TestCmd.fail_test(condition = 1) -""" % self.orig_cwd, status = 1, stderr = "FAILED test at line 4 of \n") - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') -test.run() -test.fail_test(condition = (test.status == 0)) -""" % self.orig_cwd, status = 1, stderr = "FAILED test of %s\n\tat line 6 of \n" % run_env.workpath('run')) - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(program = 'run', interpreter = 'python', description = 'xyzzy', workdir = '') -test.run() -test.fail_test(condition = (test.status == 0)) -""" % self.orig_cwd, status = 1, stderr = "FAILED test of %s [xyzzy]\n\tat line 6 of \n" % run_env.workpath('run')) - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') -test.run() -def xxx(): - sys.stderr.write("printed on failure\\n") -test.fail_test(condition = (test.status == 0), function = xxx) -""" % self.orig_cwd, status = 1, stderr = "printed on failure\nFAILED test of %s\n\tat line 8 of \n" % run_env.workpath('run')) - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -def test1(self): - self.run() - self.fail_test(condition = (self.status == 0)) -def test2(self): - test1(self) -test2(TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '')) -""" % self.orig_cwd, status = 1, stderr = "FAILED test of %s\n\tat line 6 of (test1)\n\tfrom line 8 of (test2)\n\tfrom line 9 of \n" % run_env.workpath('run')) - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -def test1(self): - self.run() - self.fail_test(condition = (self.status == 0), skip = 1) -def test2(self): - test1(self) -test2(TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '')) -""" % self.orig_cwd, status = 1, stderr = "FAILED test of %s\n\tat line 8 of (test2)\n\tfrom line 9 of \n" % run_env.workpath('run')) - - - -class interpreter_TestCase(TestCmdTestCase): - def test_interpreter(self): - """Test interpreter()""" - run_env = TestCmd.TestCmd(workdir = '') - run_env.write('run', """import sys -sys.stdout.write("run: STDOUT\\n") -sys.stderr.write("run: STDERR\\n") -""") - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - test = TestCmd.TestCmd(program = 'run', workdir = '') - test.interpreter_set('foo') - assert test.interpreter == 'foo', 'did not set interpreter' - test.interpreter_set('python') - assert test.interpreter == 'python', 'did not set interpreter' - test.run() - - - -class match_TestCase(TestCmdTestCase): - def test_match_default(self): - """Test match() default behavior""" - test = TestCmd.TestCmd() - assert test.match("abcde\n", "a.*e\n") - assert test.match("12345\nabcde\n", "1\\d+5\na.*e\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert test.match(lines, regexes) - - def test_match_custom_function(self): - """Test match() using a custom function""" - def match_length(lines, matches): - return len(lines) == len(matches) - test = TestCmd.TestCmd(match=match_length) - assert not test.match("123\n", "1\n") - assert test.match("123\n", "111\n") - assert not test.match("123\n123\n", "1\n1\n") - assert test.match("123\n123\n", "111\n111\n") - lines = ["123\n", "123\n"] - regexes = ["1\n", "1\n"] - assert test.match(lines, regexes) # due to equal numbers of lines - - def test_match_TestCmd_function(self): - """Test match() using a TestCmd function""" - test = TestCmd.TestCmd(match = TestCmd.match_exact) - assert not test.match("abcde\n", "a.*e\n") - assert test.match("abcde\n", "abcde\n") - assert not test.match("12345\nabcde\n", "1\d+5\na.*e\n") - assert test.match("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match(lines, regexes) - assert test.match(lines, lines) - - def test_match_static_method(self): - """Test match() using a static method""" - test = TestCmd.TestCmd(match=TestCmd.TestCmd.match_exact) - assert not test.match("abcde\n", "a.*e\n") - assert test.match("abcde\n", "abcde\n") - assert not test.match("12345\nabcde\n", "1\d+5\na.*e\n") - assert test.match("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match(lines, regexes) - assert test.match(lines, lines) - - def test_match_string(self): - """Test match() using a string to fetch the match method""" - test = TestCmd.TestCmd(match='match_exact') - assert not test.match("abcde\n", "a.*e\n") - assert test.match("abcde\n", "abcde\n") - assert not test.match("12345\nabcde\n", "1\d+5\na.*e\n") - assert test.match("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match(lines, regexes) - assert test.match(lines, lines) - - - -class match_exact_TestCase(TestCmdTestCase): - def test_match_exact_function(self): - """Test calling the TestCmd.match_exact() function""" - assert not TestCmd.match_exact("abcde\\n", "a.*e\\n") - assert TestCmd.match_exact("abcde\\n", "abcde\\n") - - def test_match_exact_instance_method(self): - """Test calling the TestCmd.TestCmd().match_exact() instance method""" - test = TestCmd.TestCmd() - assert not test.match_exact("abcde\\n", "a.*e\\n") - assert test.match_exact("abcde\\n", "abcde\\n") - - def test_match_exact_static_method(self): - """Test calling the TestCmd.TestCmd.match_exact() static method""" - assert not TestCmd.TestCmd.match_exact("abcde\\n", "a.*e\\n") - assert TestCmd.TestCmd.match_exact("abcde\\n", "abcde\\n") - - def test_evaluation(self): - """Test match_exact() evaluation""" - test = TestCmd.TestCmd() - assert not test.match_exact("abcde\n", "a.*e\n") - assert test.match_exact("abcde\n", "abcde\n") - assert not test.match_exact(["12345\n", "abcde\n"], ["1[0-9]*5\n", "a.*e\n"]) - assert test.match_exact(["12345\n", "abcde\n"], ["12345\n", "abcde\n"]) - assert not test.match_exact(UserList(["12345\n", "abcde\n"]), - ["1[0-9]*5\n", "a.*e\n"]) - assert test.match_exact(UserList(["12345\n", "abcde\n"]), - ["12345\n", "abcde\n"]) - assert not test.match_exact(["12345\n", "abcde\n"], - UserList(["1[0-9]*5\n", "a.*e\n"])) - assert test.match_exact(["12345\n", "abcde\n"], - UserList(["12345\n", "abcde\n"])) - assert not test.match_exact("12345\nabcde\n", "1[0-9]*5\na.*e\n") - assert test.match_exact("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match_exact(lines, regexes) - assert test.match_exact(lines, lines) - - - -class match_re_dotall_TestCase(TestCmdTestCase): - def test_match_re_dotall_function(self): - """Test calling the TestCmd.match_re_dotall() function""" - assert TestCmd.match_re_dotall("abcde\nfghij\n", "a.*j\n") - - def test_match_re_dotall_instance_method(self): - """Test calling the TestCmd.TestCmd().match_re_dotall() instance method""" - test = TestCmd.TestCmd() - test.match_re_dotall("abcde\\nfghij\\n", "a.*j\\n") - - def test_match_re_dotall_static_method(self): - """Test calling the TestCmd.TestCmd.match_re_dotall() static method""" - assert TestCmd.TestCmd.match_re_dotall("abcde\nfghij\n", "a.*j\n") - - def test_error(self): - """Test handling a compilation error in TestCmd.match_re_dotall()""" - run_env = TestCmd.TestCmd(workdir = '') - cwd = os.getcwd() - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - try: - script_input = """import sys -sys.path = ['%s'] + sys.path -import TestCmd -assert TestCmd.match_re_dotall("abcde", "a.*(e") -sys.exit(0) -""" % cwd - stdout, stderr, status = self.call_python(script_input) - assert status == 1, status - expect1 = "Regular expression error in '^a.*(e$': missing )\n" - expect2 = "Regular expression error in '^a.*(e$': unbalanced parenthesis\n" - assert (stderr.find(expect1) != -1 or - stderr.find(expect2) != -1), repr(stderr) - finally: - os.chdir(cwd) - - def test_evaluation(self): - """Test match_re_dotall() evaluation""" - test = TestCmd.TestCmd() - assert test.match_re_dotall("abcde\nfghij\n", "a.*e\nf.*j\n") - assert test.match_re_dotall("abcde\nfghij\n", "a[^j]*j\n") - assert test.match_re_dotall("abcde\nfghij\n", "abcde\nfghij\n") - assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], - ["1[0-9]*5\n", "a.*e\n", "f.*j\n"]) - assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], - ["1.*j\n"]) - assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], - ["12345\n", "abcde\n", "fghij\n"]) - assert test.match_re_dotall(UserList(["12345\n", - "abcde\n", - "fghij\n"]), - ["1[0-9]*5\n", "a.*e\n", "f.*j\n"]) - assert test.match_re_dotall(UserList(["12345\n", - "abcde\n", - "fghij\n"]), - ["1.*j\n"]) - assert test.match_re_dotall(UserList(["12345\n", - "abcde\n", - "fghij\n"]), - ["12345\n", "abcde\n", "fghij\n"]) - assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], - UserList(["1[0-9]*5\n", - "a.*e\n", - "f.*j\n"])) - assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], - UserList(["1.*j\n"])) - assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], - UserList(["12345\n", - "abcde\n", - "fghij\n"])) - assert test.match_re_dotall("12345\nabcde\nfghij\n", - "1[0-9]*5\na.*e\nf.*j\n") - assert test.match_re_dotall("12345\nabcde\nfghij\n", "1.*j\n") - assert test.match_re_dotall("12345\nabcde\nfghij\n", - "12345\nabcde\nfghij\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert test.match_re_dotall(lines, regexes) - assert test.match_re_dotall(lines, lines) - - - -class match_re_TestCase(TestCmdTestCase): - def test_match_re_function(self): - """Test calling the TestCmd.match_re() function""" - assert TestCmd.match_re("abcde\n", "a.*e\n") - - def test_match_re_instance_method(self): - """Test calling the TestCmd.TestCmd().match_re() instance method""" - test = TestCmd.TestCmd() - assert test.match_re("abcde\n", "a.*e\n") - - def test_match_re_static_method(self): - """Test calling the TestCmd.TestCmd.match_re() static method""" - assert TestCmd.TestCmd.match_re("abcde\n", "a.*e\n") - - def test_error(self): - """Test handling a compilation error in TestCmd.match_re()""" - run_env = TestCmd.TestCmd(workdir = '') - cwd = os.getcwd() - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - try: - script_input = """import sys -sys.path = ['%s'] + sys.path -import TestCmd -assert TestCmd.match_re("abcde\\n", "a.*(e\\n") -sys.exit(0) -""" % cwd - stdout, stderr, status = self.call_python(script_input) - assert status == 1, status - expect1 = "Regular expression error in '^a.*(e$': missing )\n" - expect2 = "Regular expression error in '^a.*(e$': unbalanced parenthesis\n" - assert (stderr.find(expect1) != -1 or - stderr.find(expect2) != -1), repr(stderr) - finally: - os.chdir(cwd) - - def test_evaluation(self): - """Test match_re() evaluation""" - test = TestCmd.TestCmd() - assert test.match_re("abcde\n", "a.*e\n") - assert test.match_re("abcde\n", "abcde\n") - assert test.match_re(["12345\n", "abcde\n"], ["1[0-9]*5\n", "a.*e\n"]) - assert test.match_re(["12345\n", "abcde\n"], ["12345\n", "abcde\n"]) - assert test.match_re(UserList(["12345\n", "abcde\n"]), - ["1[0-9]*5\n", "a.*e\n"]) - assert test.match_re(UserList(["12345\n", "abcde\n"]), - ["12345\n", "abcde\n"]) - assert test.match_re(["12345\n", "abcde\n"], - UserList(["1[0-9]*5\n", "a.*e\n"])) - assert test.match_re(["12345\n", "abcde\n"], - UserList(["12345\n", "abcde\n"])) - assert test.match_re("12345\nabcde\n", "1[0-9]*5\na.*e\n") - assert test.match_re("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert test.match_re(lines, regexes) - assert test.match_re(lines, lines) - - - -class match_stderr_TestCase(TestCmdTestCase): - def test_match_stderr_default(self): - """Test match_stderr() default behavior""" - test = TestCmd.TestCmd() - assert test.match_stderr("abcde\n", "a.*e\n") - assert test.match_stderr("12345\nabcde\n", "1\\d+5\na.*e\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert test.match_stderr(lines, regexes) - - def test_match_stderr_not_affecting_match_stdout(self): - """Test match_stderr() not affecting match_stdout() behavior""" - test = TestCmd.TestCmd(match_stderr=TestCmd.TestCmd.match_exact) - - assert not test.match_stderr("abcde\n", "a.*e\n") - assert test.match_stderr("abcde\n", "abcde\n") - assert not test.match_stderr("12345\nabcde\n", "1\\d+5\na.*e\n") - assert test.match_stderr("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match_stderr(lines, regexes) - assert test.match_stderr(lines, lines) - - assert test.match_stdout("abcde\n", "a.*e\n") - assert test.match_stdout("12345\nabcde\n", "1\\d+5\na.*e\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert test.match_stdout(lines, regexes) - - def test_match_stderr_custom_function(self): - """Test match_stderr() using a custom function""" - def match_length(lines, matches): - return len(lines) == len(matches) - test = TestCmd.TestCmd(match_stderr=match_length) - assert not test.match_stderr("123\n", "1\n") - assert test.match_stderr("123\n", "111\n") - assert not test.match_stderr("123\n123\n", "1\n1\n") - assert test.match_stderr("123\n123\n", "111\n111\n") - lines = ["123\n", "123\n"] - regexes = ["1\n", "1\n"] - assert test.match_stderr(lines, regexes) # equal numbers of lines - - def test_match_stderr_TestCmd_function(self): - """Test match_stderr() using a TestCmd function""" - test = TestCmd.TestCmd(match_stderr = TestCmd.match_exact) - assert not test.match_stderr("abcde\n", "a.*e\n") - assert test.match_stderr("abcde\n", "abcde\n") - assert not test.match_stderr("12345\nabcde\n", "1\d+5\na.*e\n") - assert test.match_stderr("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match_stderr(lines, regexes) - assert test.match_stderr(lines, lines) - - def test_match_stderr_static_method(self): - """Test match_stderr() using a static method""" - test = TestCmd.TestCmd(match_stderr=TestCmd.TestCmd.match_exact) - assert not test.match_stderr("abcde\n", "a.*e\n") - assert test.match_stderr("abcde\n", "abcde\n") - assert not test.match_stderr("12345\nabcde\n", "1\d+5\na.*e\n") - assert test.match_stderr("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match_stderr(lines, regexes) - assert test.match_stderr(lines, lines) - - def test_match_stderr_string(self): - """Test match_stderr() using a string to fetch the match method""" - test = TestCmd.TestCmd(match_stderr='match_exact') - assert not test.match_stderr("abcde\n", "a.*e\n") - assert test.match_stderr("abcde\n", "abcde\n") - assert not test.match_stderr("12345\nabcde\n", "1\d+5\na.*e\n") - assert test.match_stderr("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match_stderr(lines, regexes) - assert test.match_stderr(lines, lines) - - - -class match_stdout_TestCase(TestCmdTestCase): - def test_match_stdout_default(self): - """Test match_stdout() default behavior""" - test = TestCmd.TestCmd() - assert test.match_stdout("abcde\n", "a.*e\n") - assert test.match_stdout("12345\nabcde\n", "1\\d+5\na.*e\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert test.match_stdout(lines, regexes) - - def test_match_stdout_not_affecting_match_stderr(self): - """Test match_stdout() not affecting match_stderr() behavior""" - test = TestCmd.TestCmd(match_stdout=TestCmd.TestCmd.match_exact) - - assert not test.match_stdout("abcde\n", "a.*e\n") - assert test.match_stdout("abcde\n", "abcde\n") - assert not test.match_stdout("12345\nabcde\n", "1\\d+5\na.*e\n") - assert test.match_stdout("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match_stdout(lines, regexes) - assert test.match_stdout(lines, lines) - - assert test.match_stderr("abcde\n", "a.*e\n") - assert test.match_stderr("12345\nabcde\n", "1\\d+5\na.*e\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert test.match_stderr(lines, regexes) - - def test_match_stdout_custom_function(self): - """Test match_stdout() using a custom function""" - def match_length(lines, matches): - return len(lines) == len(matches) - test = TestCmd.TestCmd(match_stdout=match_length) - assert not test.match_stdout("123\n", "1\n") - assert test.match_stdout("123\n", "111\n") - assert not test.match_stdout("123\n123\n", "1\n1\n") - assert test.match_stdout("123\n123\n", "111\n111\n") - lines = ["123\n", "123\n"] - regexes = ["1\n", "1\n"] - assert test.match_stdout(lines, regexes) # equal numbers of lines - - def test_match_stdout_TestCmd_function(self): - """Test match_stdout() using a TestCmd function""" - test = TestCmd.TestCmd(match_stdout = TestCmd.match_exact) - assert not test.match_stdout("abcde\n", "a.*e\n") - assert test.match_stdout("abcde\n", "abcde\n") - assert not test.match_stdout("12345\nabcde\n", "1\d+5\na.*e\n") - assert test.match_stdout("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match_stdout(lines, regexes) - assert test.match_stdout(lines, lines) - - def test_match_stdout_static_method(self): - """Test match_stdout() using a static method""" - test = TestCmd.TestCmd(match_stdout=TestCmd.TestCmd.match_exact) - assert not test.match_stdout("abcde\n", "a.*e\n") - assert test.match_stdout("abcde\n", "abcde\n") - assert not test.match_stdout("12345\nabcde\n", "1\d+5\na.*e\n") - assert test.match_stdout("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match_stdout(lines, regexes) - assert test.match_stdout(lines, lines) - - def test_match_stdout_string(self): - """Test match_stdout() using a string to fetch the match method""" - test = TestCmd.TestCmd(match_stdout='match_exact') - assert not test.match_stdout("abcde\n", "a.*e\n") - assert test.match_stdout("abcde\n", "abcde\n") - assert not test.match_stdout("12345\nabcde\n", "1\d+5\na.*e\n") - assert test.match_stdout("12345\nabcde\n", "12345\nabcde\n") - lines = ["vwxyz\n", "67890\n"] - regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] - assert not test.match_stdout(lines, regexes) - assert test.match_stdout(lines, lines) - - - -class no_result_TestCase(TestCmdTestCase): - def test_no_result(self): - """Test no_result()""" - run_env = TestCmd.TestCmd(workdir = '') - run_env.write('run', """import sys -sys.stdout.write("run: STDOUT\\n") -sys.stderr.write("run: STDERR\\n") -""") - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -TestCmd.no_result(condition = 1) -""" % self.orig_cwd, status = 2, stderr = "NO RESULT for test at line 4 of \n") - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') -test.run() -test.no_result(condition = (test.status == 0)) -""" % self.orig_cwd, status = 2, stderr = "NO RESULT for test of %s\n\tat line 6 of \n" % run_env.workpath('run')) - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(program = 'run', interpreter = 'python', description = 'xyzzy', workdir = '') -test.run() -test.no_result(condition = (test.status == 0)) -""" % self.orig_cwd, status = 2, stderr = "NO RESULT for test of %s [xyzzy]\n\tat line 6 of \n" % run_env.workpath('run')) - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') -test.run() -def xxx(): - sys.stderr.write("printed on no result\\n") -test.no_result(condition = (test.status == 0), function = xxx) -""" % self.orig_cwd, status = 2, stderr = "printed on no result\nNO RESULT for test of %s\n\tat line 8 of \n" % run_env.workpath('run')) - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -def test1(self): - self.run() - self.no_result(condition = (self.status == 0)) -def test2(self): - test1(self) -test2(TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '')) -""" % self.orig_cwd, status = 2, stderr = "NO RESULT for test of %s\n\tat line 6 of (test1)\n\tfrom line 8 of (test2)\n\tfrom line 9 of \n" % run_env.workpath('run')) - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -def test1(self): - self.run() - self.no_result(condition = (self.status == 0), skip = 1) -def test2(self): - test1(self) -test2(TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '')) -""" % self.orig_cwd, status = 2, stderr = "NO RESULT for test of %s\n\tat line 8 of (test2)\n\tfrom line 9 of \n" % run_env.workpath('run')) - - - -class pass_test_TestCase(TestCmdTestCase): - def test_pass_test(self): - """Test pass_test()""" - run_env = TestCmd.TestCmd(workdir = '') - run_env.write('run', """import sys -sys.stdout.write("run: STDOUT\\n") -sys.stderr.write("run: STDERR\\n") -""") - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -TestCmd.pass_test(condition = 1) -""" % self.orig_cwd, stderr = "PASSED\n") - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') -test.run() -test.pass_test(condition = (test.status == 0)) -""" % self.orig_cwd, stderr = "PASSED\n") - - self.popen_python("""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') -test.run() -def brag(): - sys.stderr.write("printed on success\\n") -test.pass_test(condition = (test.status == 0), function = brag) -""" % self.orig_cwd, stderr = "printed on success\nPASSED\n") - - # TODO(sgk): SHOULD ALSO TEST FAILURE CONDITIONS - - - -class preserve_TestCase(TestCmdTestCase): - def test_preserve(self): - """Test preserve()""" - def cleanup_test(test, cond=None, stdout=""): - io = StringIO() - save = sys.stdout - sys.stdout = io - try: - if cond: - test.cleanup(cond) - else: - test.cleanup() - o = io.getvalue() - assert o == stdout, "o = `%s', stdout = `%s'" % (o, stdout) - finally: - sys.stdout = save - - test = TestCmd.TestCmd(workdir = '') - wdir = test.workdir - try: - test.write('file1', "Test file #1\n") - #test.cleanup() - cleanup_test(test, ) - assert not os.path.exists(wdir) - finally: - if os.path.exists(wdir): - shutil.rmtree(wdir, ignore_errors = 1) - test._dirlist.remove(wdir) - - test = TestCmd.TestCmd(workdir = '') - wdir = test.workdir - try: - test.write('file2', "Test file #2\n") - test.preserve('pass_test') - cleanup_test(test, 'pass_test', "Preserved directory %s\n" % wdir) - assert os.path.isdir(wdir) - cleanup_test(test, 'fail_test') - assert not os.path.exists(wdir) - finally: - if os.path.exists(wdir): - shutil.rmtree(wdir, ignore_errors = 1) - test._dirlist.remove(wdir) - - test = TestCmd.TestCmd(workdir = '') - wdir = test.workdir - try: - test.write('file3', "Test file #3\n") - test.preserve('fail_test') - cleanup_test(test, 'fail_test', "Preserved directory %s\n" % wdir) - assert os.path.isdir(wdir) - cleanup_test(test, 'pass_test') - assert not os.path.exists(wdir) - finally: - if os.path.exists(wdir): - shutil.rmtree(wdir, ignore_errors = 1) - test._dirlist.remove(wdir) - - test = TestCmd.TestCmd(workdir = '') - wdir = test.workdir - try: - test.write('file4', "Test file #4\n") - test.preserve('fail_test', 'no_result') - cleanup_test(test, 'fail_test', "Preserved directory %s\n" % wdir) - assert os.path.isdir(wdir) - cleanup_test(test, 'no_result', "Preserved directory %s\n" % wdir) - assert os.path.isdir(wdir) - cleanup_test(test, 'pass_test') - assert not os.path.exists(wdir) - finally: - if os.path.exists(wdir): - shutil.rmtree(wdir, ignore_errors = 1) - test._dirlist.remove(wdir) - - test = TestCmd.TestCmd(workdir = '') - wdir = test.workdir - try: - test.preserve() - cleanup_test(test, 'pass_test', "Preserved directory %s\n" % wdir) - assert os.path.isdir(wdir) - cleanup_test(test, 'fail_test', "Preserved directory %s\n" % wdir) - assert os.path.isdir(wdir) - cleanup_test(test, 'no_result', "Preserved directory %s\n" % wdir) - assert os.path.isdir(wdir) - finally: - if os.path.exists(wdir): - shutil.rmtree(wdir, ignore_errors = 1) - test._dirlist.remove(wdir) - - - -class program_TestCase(TestCmdTestCase): - def test_program(self): - """Test program()""" - test = TestCmd.TestCmd() - assert test.program is None, 'initialized program?' - test = TestCmd.TestCmd(program = 'test') - assert test.program == os.path.join(os.getcwd(), 'test'), 'uninitialized program' - test.program_set('foo') - assert test.program == os.path.join(os.getcwd(), 'foo'), 'did not set program' - - - -class read_TestCase(TestCmdTestCase): - def test_read(self): - """Test read()""" - test = TestCmd.TestCmd(workdir = '', subdir = 'foo') - wdir_file1 = os.path.join(test.workdir, 'file1') - wdir_file2 = os.path.join(test.workdir, 'file2') - wdir_foo_file3 = os.path.join(test.workdir, 'foo', 'file3') - wdir_file4 = os.path.join(test.workdir, 'file4') - wdir_file5 = os.path.join(test.workdir, 'file5') - - open(wdir_file1, 'wb').write("") - open(wdir_file2, 'wb').write("Test\nfile\n#2.\n") - open(wdir_foo_file3, 'wb').write("Test\nfile\n#3.\n") - open(wdir_file4, 'wb').write("Test\nfile\n#4.\n") - open(wdir_file5, 'wb').write("Test\r\nfile\r\n#5.\r\n") - - try: - contents = test.read('no_file') - except IOError: # expect "No such file or directory" - pass - except: - raise - - try: - test.read(test.workpath('file_x'), mode = 'w') - except ValueError: # expect "mode must begin with 'r' - pass - except: - raise - - def _file_matches(file, contents, expected): - assert contents == expected, \ - "Expected contents of " + str(file) + "==========\n" + \ - expected + \ - "Actual contents of " + str(file) + "============\n" + \ - contents - - _file_matches(wdir_file1, test.read('file1'), "") - _file_matches(wdir_file2, test.read('file2'), "Test\nfile\n#2.\n") - _file_matches(wdir_foo_file3, test.read(['foo', 'file3']), - "Test\nfile\n#3.\n") - _file_matches(wdir_foo_file3, - test.read(UserList(['foo', 'file3'])), - "Test\nfile\n#3.\n") - _file_matches(wdir_file4, test.read('file4', mode = 'r'), - "Test\nfile\n#4.\n") - _file_matches(wdir_file5, test.read('file5', mode = 'rb'), - "Test\r\nfile\r\n#5.\r\n") - - - -class rmdir_TestCase(TestCmdTestCase): - def test_rmdir(self): - """Test rmdir()""" - test = TestCmd.TestCmd(workdir = '') - - try: - test.rmdir(['no', 'such', 'dir']) - except EnvironmentError: - pass - else: - raise Exception("did not catch expected EnvironmentError") - - test.subdir(['sub'], - ['sub', 'dir'], - ['sub', 'dir', 'one']) - - s = test.workpath('sub') - s_d = test.workpath('sub', 'dir') - s_d_o = test.workpath('sub', 'dir', 'one') - - try: - test.rmdir(['sub']) - except EnvironmentError: - pass - else: - raise Exception("did not catch expected EnvironmentError") - - assert os.path.isdir(s_d_o), "%s is gone?" % s_d_o - - try: - test.rmdir(['sub']) - except EnvironmentError: - pass - else: - raise Exception("did not catch expected EnvironmentError") - - assert os.path.isdir(s_d_o), "%s is gone?" % s_d_o - - test.rmdir(['sub', 'dir', 'one']) - - assert not os.path.exists(s_d_o), "%s exists?" % s_d_o - assert os.path.isdir(s_d), "%s is gone?" % s_d - - test.rmdir(['sub', 'dir']) - - assert not os.path.exists(s_d), "%s exists?" % s_d - assert os.path.isdir(s), "%s is gone?" % s - - test.rmdir('sub') - - assert not os.path.exists(s), "%s exists?" % s - - - -class run_TestCase(TestCmdTestCase): - def test_run(self): - """Test run()""" - - t = self.setup_run_scripts() - - # Everything before this prepared our "source directory." - # Now do the real test. - try: - test = TestCmd.TestCmd(program = t.script, - interpreter = 'python', - workdir = '', - subdir = 'script_subdir') - - test.run() - self.run_match(test.stdout(), t.script, "STDOUT", t.workdir, - repr([])) - self.run_match(test.stderr(), t.script, "STDERR", t.workdir, - repr([])) - - test.run(arguments = 'arg1 arg2 arg3') - self.run_match(test.stdout(), t.script, "STDOUT", t.workdir, - repr(['arg1', 'arg2', 'arg3'])) - self.run_match(test.stderr(), t.script, "STDERR", t.workdir, - repr(['arg1', 'arg2', 'arg3'])) - - test.run(program = t.scriptx, arguments = 'foo') - self.run_match(test.stdout(), t.scriptx, "STDOUT", t.workdir, - repr(['foo'])) - self.run_match(test.stderr(), t.scriptx, "STDERR", t.workdir, - repr(['foo'])) - - test.run(chdir = os.curdir, arguments = 'x y z') - self.run_match(test.stdout(), t.script, "STDOUT", test.workdir, - repr(['x', 'y', 'z'])) - self.run_match(test.stderr(), t.script, "STDERR", test.workdir, - repr(['x', 'y', 'z'])) - - test.run(chdir = 'script_subdir') - script_subdir = test.workpath('script_subdir') - self.run_match(test.stdout(), t.script, "STDOUT", script_subdir, - repr([])) - self.run_match(test.stderr(), t.script, "STDERR", script_subdir, - repr([])) - - test.run(program = t.script1, interpreter = ['python', '-x']) - self.run_match(test.stdout(), t.script1, "STDOUT", t.workdir, - repr([])) - self.run_match(test.stderr(), t.script1, "STDERR", t.workdir, - repr([])) - - try: - test.run(chdir = 'no_subdir') - except OSError: - pass - - test.run(program = 'no_script', interpreter = 'python') - assert test.status != None, test.status - - try: - test.run(program = 'no_script', interpreter = 'no_interpreter') - except OSError: - # Python versions that use subprocess throw an OSError - # exception when they try to execute something that - # isn't there. - pass - else: - # Python versions that use os.popen3() or the Popen3 - # class run things through the shell, which just returns - # a non-zero exit status. - assert test.status != None, test.status - - testx = TestCmd.TestCmd(program = t.scriptx, - workdir = '', - subdir = 't.scriptx_subdir') - - testx.run() - self.run_match(testx.stdout(), t.scriptx, "STDOUT", t.workdir, - repr([])) - self.run_match(testx.stderr(), t.scriptx, "STDERR", t.workdir, - repr([])) - - testx.run(arguments = 'foo bar') - self.run_match(testx.stdout(), t.scriptx, "STDOUT", t.workdir, - repr(['foo', 'bar'])) - self.run_match(testx.stderr(), t.scriptx, "STDERR", t.workdir, - repr(['foo', 'bar'])) - - testx.run(program = t.script, interpreter = 'python', arguments = 'bar') - self.run_match(testx.stdout(), t.script, "STDOUT", t.workdir, - repr(['bar'])) - self.run_match(testx.stderr(), t.script, "STDERR", t.workdir, - repr(['bar'])) - - testx.run(chdir = os.curdir, arguments = 'baz') - self.run_match(testx.stdout(), t.scriptx, "STDOUT", testx.workdir, - repr(['baz'])) - self.run_match(testx.stderr(), t.scriptx, "STDERR", testx.workdir, - repr(['baz'])) - - testx.run(chdir = 't.scriptx_subdir') - t.scriptx_subdir = testx.workpath('t.scriptx_subdir') - self.run_match(testx.stdout(), t.scriptx, "STDOUT", t.scriptx_subdir, - repr([])) - self.run_match(testx.stderr(), t.scriptx, "STDERR", t.scriptx_subdir, - repr([])) - - testx.run(program = t.script1, interpreter = ('python', '-x')) - self.run_match(testx.stdout(), t.script1, "STDOUT", t.workdir, - repr([])) - self.run_match(testx.stderr(), t.script1, "STDERR", t.workdir, - repr([])) - - s = os.path.join('.', t.scriptx) - testx.run(program = [s]) - self.run_match(testx.stdout(), t.scriptx, "STDOUT", t.workdir, - repr([])) - self.run_match(testx.stderr(), t.scriptx, "STDERR", t.workdir, - repr([])) - - try: - testx.run(chdir = 'no_subdir') - except OSError: - pass - - try: - testx.run(program = 'no_program') - except OSError: - # Python versions that use subprocess throw an OSError - # exception when they try to execute something that - # isn't there. - pass - else: - # Python versions that use os.popen3() or the Popen3 - # class run things through the shell, which just returns - # a non-zero exit status. - assert test.status != None - - test1 = TestCmd.TestCmd(program = t.script1, - interpreter = ['python', '-x'], - workdir = '') - - test1.run() - self.run_match(test1.stdout(), t.script1, "STDOUT", t.workdir, - repr([])) - self.run_match(test1.stderr(), t.script1, "STDERR", t.workdir, - repr([])) - - finally: - os.chdir(t.orig_cwd) - - def test_run_subclass(self): - """Test run() through a subclass with different signatures""" - - t = self.setup_run_scripts() - - # Everything before this prepared our "source directory." - # Now do the real test. - - class MyTestCmdSubclass(TestCmd.TestCmd): - def start(self, additional_argument=None, **kw): - return TestCmd.TestCmd.start(self, **kw) - - try: - test = MyTestCmdSubclass(program = t.script, - interpreter = 'python', - workdir = '', - subdir = 'script_subdir') - - test.run() - self.run_match(test.stdout(), t.script, "STDOUT", t.workdir, - repr([])) - self.run_match(test.stderr(), t.script, "STDERR", t.workdir, - repr([])) - finally: - os.chdir(t.orig_cwd) - - -class run_verbose_TestCase(TestCmdTestCase): - def test_run_verbose(self): - """Test the run() method's verbose attribute""" - - # Prepare our "source directory." - t = self.setup_run_scripts() - - save_stdout = sys.stderr - save_stderr = sys.stderr - - try: - # Test calling TestCmd() with an explicit verbose = 1. - - test = TestCmd.TestCmd(program = t.script, - interpreter = 'python', - workdir = '', - verbose = 1) - - sys.stdout = StringIO() - sys.stderr = StringIO() - - test.run(arguments = ['arg1 arg2']) - o = sys.stdout.getvalue() - assert o == '', o - e = sys.stderr.getvalue() - expect = 'python "%s" "arg1 arg2"\n' % t.script_path - assert expect == e, (expect, e) - - testx = TestCmd.TestCmd(program = t.scriptx, - workdir = '', - verbose = 1) - - sys.stdout = StringIO() - sys.stderr = StringIO() - - testx.run(arguments = ['arg1 arg2']) - expect = '"%s" "arg1 arg2"\n' % t.scriptx_path - o = sys.stdout.getvalue() - assert o == '', o - e = sys.stderr.getvalue() - assert expect == e, (expect, e) - - # Test calling TestCmd() with an explicit verbose = 2. - - outerr_fmt = """\ -============ STATUS: 0 -============ BEGIN STDOUT (len=%s): -%s============ END STDOUT -============ BEGIN STDERR (len=%s) -%s============ END STDERR -""" - - out_fmt = """\ -============ STATUS: 0 -============ BEGIN STDOUT (len=%s): -%s============ END STDOUT -""" - - err_fmt = """\ -============ STATUS: 0 -============ BEGIN STDERR (len=%s) -%s============ END STDERR -""" - - test = TestCmd.TestCmd(program = t.script, - interpreter = 'python', - workdir = '', - verbose = 2) - - sys.stdout = StringIO() - sys.stderr = StringIO() - - test.run(arguments = ['arg1 arg2']) - - line_fmt = "script: %s: %s: ['arg1 arg2']\n" - stdout_line = line_fmt % ('STDOUT', t.sub_dir) - stderr_line = line_fmt % ('STDERR', t.sub_dir) - expect = outerr_fmt % (len(stdout_line), stdout_line, - len(stderr_line), stderr_line) - o = sys.stdout.getvalue() - assert expect == o, (expect, o) - - expect = 'python "%s" "arg1 arg2"\n' % t.script_path - e = sys.stderr.getvalue() - assert e == expect, (e, expect) - - testx = TestCmd.TestCmd(program = t.scriptx, - workdir = '', - verbose = 2) - - sys.stdout = StringIO() - sys.stderr = StringIO() - - testx.run(arguments = ['arg1 arg2']) - - line_fmt = "scriptx.bat: %s: %s: ['arg1 arg2']\n" - stdout_line = line_fmt % ('STDOUT', t.sub_dir) - stderr_line = line_fmt % ('STDERR', t.sub_dir) - expect = outerr_fmt % (len(stdout_line), stdout_line, - len(stderr_line), stderr_line) - o = sys.stdout.getvalue() - assert expect == o, (expect, o) - - expect = '"%s" "arg1 arg2"\n' % t.scriptx_path - e = sys.stderr.getvalue() - assert e == expect, (e, expect) - - # Test calling TestCmd() with an explicit verbose = 3. - - test = TestCmd.TestCmd(program = t.scriptout, - interpreter = 'python', - workdir = '', - verbose = 2) - - sys.stdout = StringIO() - sys.stderr = StringIO() - - test.run(arguments = ['arg1 arg2']) - - line_fmt = "scriptout: %s: %s: ['arg1 arg2']\n" - stdout_line = line_fmt % ('STDOUT', t.sub_dir) - expect = out_fmt % (len(stdout_line), stdout_line) - o = sys.stdout.getvalue() - assert expect == o, (expect, o) - - e = sys.stderr.getvalue() - expect = 'python "%s" "arg1 arg2"\n' % (t.scriptout_path) - assert e == expect, (e, expect) - - test = TestCmd.TestCmd(program = t.scriptout, - interpreter = 'python', - workdir = '', - verbose = 3) - - sys.stdout = StringIO() - sys.stderr = StringIO() - - test.run(arguments = ['arg1 arg2']) - - line_fmt = "scriptout: %s: %s: ['arg1 arg2']\n" - stdout_line = line_fmt % ('STDOUT', t.sub_dir) - expect = outerr_fmt % (len(stdout_line), stdout_line, - '0', '') - o = sys.stdout.getvalue() - assert expect == o, (expect, o) - - e = sys.stderr.getvalue() - expect = 'python "%s" "arg1 arg2"\n' % (t.scriptout_path) - assert e == expect, (e, expect) - - # Test letting TestCmd() pick up verbose = 2 from the environment. - - os.environ['TESTCMD_VERBOSE'] = '2' - - test = TestCmd.TestCmd(program = t.script, - interpreter = 'python', - workdir = '') - - sys.stdout = StringIO() - sys.stderr = StringIO() - - test.run(arguments = ['arg1 arg2']) - - line_fmt = "script: %s: %s: ['arg1 arg2']\n" - stdout_line = line_fmt % ('STDOUT', t.sub_dir) - stderr_line = line_fmt % ('STDERR', t.sub_dir) - expect = outerr_fmt % (len(stdout_line), stdout_line, - len(stderr_line), stderr_line) - o = sys.stdout.getvalue() - assert expect == o, (expect, o) - - expect = 'python "%s" "arg1 arg2"\n' % t.script_path - e = sys.stderr.getvalue() - assert e == expect, (e, expect) - - testx = TestCmd.TestCmd(program = t.scriptx, - workdir = '') - - sys.stdout = StringIO() - sys.stderr = StringIO() - - testx.run(arguments = ['arg1 arg2']) - - line_fmt = "scriptx.bat: %s: %s: ['arg1 arg2']\n" - stdout_line = line_fmt % ('STDOUT', t.sub_dir) - stderr_line = line_fmt % ('STDERR', t.sub_dir) - expect = outerr_fmt % (len(stdout_line), stdout_line, - len(stderr_line), stderr_line) - o = sys.stdout.getvalue() - assert expect == o, (expect, o) - - expect = '"%s" "arg1 arg2"\n' % t.scriptx_path - e = sys.stderr.getvalue() - assert e == expect, (e, expect) - - # Test letting TestCmd() pick up verbose = 1 from the environment. - - os.environ['TESTCMD_VERBOSE'] = '1' - - test = TestCmd.TestCmd(program = t.script, - interpreter = 'python', - workdir = '', - verbose = 1) - - sys.stdout = StringIO() - sys.stderr = StringIO() - - test.run(arguments = ['arg1 arg2']) - o = sys.stdout.getvalue() - assert o == '', o - e = sys.stderr.getvalue() - expect = 'python "%s" "arg1 arg2"\n' % t.script_path - assert expect == e, (expect, e) - - testx = TestCmd.TestCmd(program = t.scriptx, - workdir = '', - verbose = 1) - - sys.stdout = StringIO() - sys.stderr = StringIO() - - testx.run(arguments = ['arg1 arg2']) - expect = '"%s" "arg1 arg2"\n' % t.scriptx_path - o = sys.stdout.getvalue() - assert o == '', o - e = sys.stderr.getvalue() - assert expect == e, (expect, e) - - finally: - sys.stdout = save_stdout - sys.stderr = save_stderr - os.chdir(t.orig_cwd) - os.environ['TESTCMD_VERBOSE'] = '' - - - -class set_diff_function_TestCase(TestCmdTestCase): - def test_set_diff_function(self): - """Test set_diff_function()""" - self.popen_python(r"""import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd() -test.diff("a\n", "a\n") -test.set_diff_function('diff_re') -test.diff(".\n", "a\n") -sys.exit(0) -""" % self.orig_cwd) - - def test_set_diff_function_stdout(self): - """Test set_diff_function(): stdout""" - self.popen_python("""from __future__ import print_function -import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd() -print("diff:") -test.diff("a\\n", "a\\n") -print("diff_stdout:") -test.diff_stdout("a\\n", "a\\n") -test.set_diff_function(stdout='diff_re') -print("diff:") -test.diff(".\\n", "a\\n") -print("diff_stdout:") -test.diff_stdout(".\\n", "a\\n") -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -diff: -diff_stdout: -diff: -1c1 -< . ---- -> a -diff_stdout: -""") - - def test_set_diff_function_stderr(self): - """Test set_diff_function(): stderr """ - self.popen_python("""from __future__ import print_function -import sys -sys.path = ['%s'] + sys.path -import TestCmd -test = TestCmd.TestCmd() -print("diff:") -test.diff("a\\n", "a\\n") -print("diff_stderr:") -test.diff_stderr("a\\n", "a\\n") -test.set_diff_function(stderr='diff_re') -print("diff:") -test.diff(".\\n", "a\\n") -print("diff_stderr:") -test.diff_stderr(".\\n", "a\\n") -sys.exit(0) -""" % self.orig_cwd, - stdout="""\ -diff: -diff_stderr: -diff: -1c1 -< . ---- -> a -diff_stderr: -""") - - - -class set_match_function_TestCase(TestCmdTestCase): - def test_set_match_function(self): - """Test set_match_function()""" - test = TestCmd.TestCmd() - assert test.match("abcde\n", "a.*e\n") - assert test.match("abcde\n", "abcde\n") - - test.set_match_function('match_exact') - - assert not test.match("abcde\n", "a.*e\n") - assert test.match("abcde\n", "abcde\n") - - def test_set_match_function_stdout(self): - """Test set_match_function(): stdout """ - test = TestCmd.TestCmd() - assert test.match("abcde\n", "a.*e\n") - assert test.match("abcde\n", "abcde\n") - assert test.match_stdout("abcde\n", "a.*e\n") - assert test.match_stdout("abcde\n", "abcde\n") - - test.set_match_function(stdout='match_exact') - - assert test.match("abcde\n", "a.*e\n") - assert test.match("abcde\n", "abcde\n") - assert not test.match_stdout("abcde\n", "a.*e\n") - assert test.match_stdout("abcde\n", "abcde\n") - - def test_set_match_function_stderr(self): - """Test set_match_function(): stderr """ - test = TestCmd.TestCmd() - assert test.match("abcde\n", "a.*e\n") - assert test.match("abcde\n", "abcde\n") - assert test.match_stderr("abcde\n", "a.*e\n") - assert test.match_stderr("abcde\n", "abcde\n") - - test.set_match_function(stderr='match_exact') - - assert test.match("abcde\n", "a.*e\n") - assert test.match("abcde\n", "abcde\n") - assert not test.match_stderr("abcde\n", "a.*e\n") - assert test.match_stderr("abcde\n", "abcde\n") - - - -class sleep_TestCase(TestCmdTestCase): - def test_sleep(self): - """Test sleep()""" - test = TestCmd.TestCmd() - - start = time.time() - test.sleep() - end = time.time() - diff = end - start - assert diff > 0.9, "only slept %f seconds (start %f, end %f), not default" % (diff, start, end) - - start = time.time() - test.sleep(3) - end = time.time() - diff = end - start - assert diff > 2.9, "only slept %f seconds (start %f, end %f), not 3" % (diff, start, end) - - - -class stderr_TestCase(TestCmdTestCase): - def test_stderr(self): - """Test stderr()""" - run_env = TestCmd.TestCmd(workdir = '') - run_env.write('run1', """import sys -sys.stdout.write("run1 STDOUT %s\\n" % sys.argv[1:]) -sys.stdout.write("run1 STDOUT second line\\n") -sys.stderr.write("run1 STDERR %s\\n" % sys.argv[1:]) -sys.stderr.write("run1 STDERR second line\\n") -""") - run_env.write('run2', """import sys -sys.stdout.write("run2 STDOUT %s\\n" % sys.argv[1:]) -sys.stdout.write("run2 STDOUT second line\\n") -sys.stderr.write("run2 STDERR %s\\n" % sys.argv[1:]) -sys.stderr.write("run2 STDERR second line\\n") -""") - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - test = TestCmd.TestCmd(interpreter = 'python', workdir = '') - try: - output = test.stderr() - except IndexError: - pass - else: - raise IndexError("got unexpected output:\n" + output) - test.program_set('run1') - test.run(arguments = 'foo bar') - test.program_set('run2') - test.run(arguments = 'snafu') - # XXX SHOULD TEST ABSOLUTE NUMBER AS WELL - output = test.stderr() - assert output == "run2 STDERR ['snafu']\nrun2 STDERR second line\n", output - output = test.stderr(run = -1) - assert output == "run1 STDERR ['foo', 'bar']\nrun1 STDERR second line\n", output - - - -class command_args_TestCase(TestCmdTestCase): - def test_command_args(self): - """Test command_args()""" - run_env = TestCmd.TestCmd(workdir = '') - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - test = TestCmd.TestCmd(workdir = '') - - r = test.command_args('prog') - expect = [run_env.workpath('prog')] - assert r == expect, (expect, r) - - r = test.command_args(test.workpath('new_prog')) - expect = [test.workpath('new_prog')] - assert r == expect, (expect, r) - - r = test.command_args('prog', 'python') - expect = ['python', run_env.workpath('prog')] - assert r == expect, (expect, r) - - r = test.command_args('prog', 'python', 'arg1 arg2') - expect = ['python', run_env.workpath('prog'), 'arg1', 'arg2'] - assert r == expect, (expect, r) - - test.program_set('default_prog') - default_prog = run_env.workpath('default_prog') - - r = test.command_args() - expect = [default_prog] - assert r == expect, (expect, r) - - r = test.command_args(interpreter='PYTHON') - expect = ['PYTHON', default_prog] - assert r == expect, (expect, r) - - r = test.command_args(interpreter='PYTHON', arguments='arg3 arg4') - expect = ['PYTHON', default_prog, 'arg3', 'arg4'] - assert r == expect, (expect, r) - - test.interpreter_set('default_python') - - r = test.command_args() - expect = ['default_python', default_prog] - assert r == expect, (expect, r) - - r = test.command_args(arguments='arg5 arg6') - expect = ['default_python', default_prog, 'arg5', 'arg6'] - assert r == expect, (expect, r) - - r = test.command_args('new_prog_1') - expect = [run_env.workpath('new_prog_1')] - assert r == expect, (expect, r) - - r = test.command_args(program='new_prog_2') - expect = [run_env.workpath('new_prog_2')] - assert r == expect, (expect, r) - - - -class start_TestCase(TestCmdTestCase): - def setup_run_scripts(self): - t = TestCmdTestCase.setup_run_scripts(self) - t.recv_script = 'script_recv' - t.recv_script_path = t.run_env.workpath(t.sub_dir, t.recv_script) - t.recv_out_path = t.run_env.workpath('script_recv.out') - text = """\ -import os -import sys - -class Unbuffered: - def __init__(self, file): - self.file = file - def write(self, arg): - self.file.write(arg) - self.file.flush() - def __getattr__(self, attr): - return getattr(self.file, attr) - -sys.stdout = Unbuffered(sys.stdout) -sys.stderr = Unbuffered(sys.stderr) - -sys.stdout.write('script_recv: STDOUT\\n') -sys.stderr.write('script_recv: STDERR\\n') -logfp = open(r'%s', 'wb') -while 1: - line = sys.stdin.readline() - if not line: - break - logfp.write('script_recv: ' + line) - sys.stdout.write('script_recv: STDOUT: ' + line) - sys.stderr.write('script_recv: STDERR: ' + line) -logfp.close() - """ % t.recv_out_path - t.run_env.write(t.recv_script_path, text) - os.chmod(t.recv_script_path, 0o644) # XXX UNIX-specific - return t - - def test_start(self): - """Test start()""" - - t = self.setup_run_scripts() - - # Everything before this prepared our "source directory." - # Now do the real test. - try: - test = TestCmd.TestCmd(program = t.script, - interpreter = 'python', - workdir = '', - subdir = 'script_subdir') - - p = test.start() - self.run_match(p.stdout.read(), t.script, "STDOUT", t.workdir, - repr([])) - self.run_match(p.stderr.read(), t.script, "STDERR", t.workdir, - repr([])) - p.wait() - - p = test.start(arguments='arg1 arg2 arg3') - self.run_match(p.stdout.read(), t.script, "STDOUT", t.workdir, - repr(['arg1', 'arg2', 'arg3'])) - self.run_match(p.stderr.read(), t.script, "STDERR", t.workdir, - repr(['arg1', 'arg2', 'arg3'])) - p.wait() - - p = test.start(program=t.scriptx, arguments='foo') - self.run_match(p.stdout.read(), t.scriptx, "STDOUT", t.workdir, - repr(['foo'])) - self.run_match(p.stderr.read(), t.scriptx, "STDERR", t.workdir, - repr(['foo'])) - p.wait() - - p = test.start(program=t.script1, interpreter=['python', '-x']) - self.run_match(p.stdout.read(), t.script1, "STDOUT", t.workdir, - repr([])) - self.run_match(p.stderr.read(), t.script1, "STDERR", t.workdir, - repr([])) - p.wait() - - p = test.start(program='no_script', interpreter='python') - status = p.wait() - assert status != None, status - - try: - p = test.start(program='no_script', interpreter='no_interpreter') - except OSError: - # Python versions that use subprocess throw an OSError - # exception when they try to execute something that - # isn't there. - pass - else: - status = p.wait() - # Python versions that use os.popen3() or the Popen3 - # class run things through the shell, which just returns - # a non-zero exit status. - assert status != None, status - - testx = TestCmd.TestCmd(program = t.scriptx, - workdir = '', - subdir = 't.scriptx_subdir') - - p = testx.start() - self.run_match(p.stdout.read(), t.scriptx, "STDOUT", t.workdir, - repr([])) - self.run_match(p.stderr.read(), t.scriptx, "STDERR", t.workdir, - repr([])) - p.wait() - - p = testx.start(arguments='foo bar') - self.run_match(p.stdout.read(), t.scriptx, "STDOUT", t.workdir, - repr(['foo', 'bar'])) - self.run_match(p.stderr.read(), t.scriptx, "STDERR", t.workdir, - repr(['foo', 'bar'])) - p.wait() - - p = testx.start(program=t.script, interpreter='python', arguments='bar') - self.run_match(p.stdout.read(), t.script, "STDOUT", t.workdir, - repr(['bar'])) - self.run_match(p.stderr.read(), t.script, "STDERR", t.workdir, - repr(['bar'])) - p.wait() - - p = testx.start(program=t.script1, interpreter=('python', '-x')) - self.run_match(p.stdout.read(), t.script1, "STDOUT", t.workdir, - repr([])) - self.run_match(p.stderr.read(), t.script1, "STDERR", t.workdir, - repr([])) - p.wait() - - s = os.path.join('.', t.scriptx) - p = testx.start(program=[s]) - self.run_match(p.stdout.read(), t.scriptx, "STDOUT", t.workdir, - repr([])) - self.run_match(p.stderr.read(), t.scriptx, "STDERR", t.workdir, - repr([])) - p.wait() - - try: - testx.start(program='no_program') - except OSError: - # Python versions that use subprocess throw an OSError - # exception when they try to execute something that - # isn't there. - pass - else: - # Python versions that use os.popen3() or the Popen3 - # class run things through the shell, which just dies - # trying to execute the non-existent program before - # we can wait() for it. - try: - p = p.wait() - except OSError: - pass - - test1 = TestCmd.TestCmd(program = t.script1, - interpreter = ['python', '-x'], - workdir = '') - - p = test1.start() - self.run_match(p.stdout.read(), t.script1, "STDOUT", t.workdir, - repr([])) - self.run_match(p.stderr.read(), t.script1, "STDERR", t.workdir, - repr([])) - p.wait() - - finally: - os.chdir(t.orig_cwd) - - def test_finish(self): - """Test finish()""" - - t = self.setup_run_scripts() - - # Everything before this prepared our "source directory." - # Now do the real test. - try: - - test = TestCmd.TestCmd(program = t.recv_script, - interpreter = 'python', - workdir = '', - subdir = 'script_subdir') - - test.start(stdin=1) - test.finish() - expect_stdout = """\ -script_recv: STDOUT -""" - expect_stderr = """\ -script_recv: STDERR -""" - stdout = test.stdout() - assert stdout == expect_stdout, stdout - stderr = test.stderr() - assert stderr == expect_stderr, stderr - - p = test.start(stdin=1) - p.send('input\n') - test.finish(p) - expect_stdout = """\ -script_recv: STDOUT -script_recv: STDOUT: input -""" - expect_stderr = """\ -script_recv: STDERR -script_recv: STDERR: input -""" - stdout = test.stdout() - assert stdout == expect_stdout, stdout - stderr = test.stderr() - assert stderr == expect_stderr, stderr - - p = test.start(combine=1, stdin=1) - p.send('input\n') - test.finish(p) - expect_stdout = """\ -script_recv: STDOUT -script_recv: STDERR -script_recv: STDOUT: input -script_recv: STDERR: input -""" - expect_stderr = "" - stdout = test.stdout() - assert stdout == expect_stdout, stdout - stderr = test.stderr() - assert stderr == expect_stderr, stderr - - finally: - os.chdir(t.orig_cwd) - - def test_recv(self): - """Test the recv() method of objects returned by start()""" - - t = self.setup_run_scripts() - - # Everything before this prepared our "source directory." - # Now do the real test. - try: - test = TestCmd.TestCmd(program = t.script, - interpreter = 'python', - workdir = '', - subdir = 'script_subdir') - - p = test.start() - stdout = p.recv() - while stdout == '': - import time - time.sleep(1) - stdout = p.recv() - self.run_match(stdout, t.script, "STDOUT", t.workdir, - repr([])) - p.wait() - - finally: - os.chdir(t.orig_cwd) - - def test_recv_err(self): - """Test the recv_err() method of objects returned by start()""" - - t = self.setup_run_scripts() - - # Everything before this prepared our "source directory." - # Now do the real test. - try: - - test = TestCmd.TestCmd(program = t.script, - interpreter = 'python', - workdir = '', - subdir = 'script_subdir') - - p = test.start() - stderr = p.recv_err() - while stderr == '': - import time - time.sleep(1) - stderr = p.recv_err() - self.run_match(stderr, t.script, "STDERR", t.workdir, - repr([])) - p.wait() - - - finally: - os.chdir(t.orig_cwd) - - def test_send(self): - """Test the send() method of objects returned by start()""" - - t = self.setup_run_scripts() - - # Everything before this prepared our "source directory." - # Now do the real test. - try: - - test = TestCmd.TestCmd(program = t.recv_script, - interpreter = 'python', - workdir = '', - subdir = 'script_subdir') - - p = test.start(stdin=1) - input = 'stdin.write() input to the receive script\n' - p.stdin.write(input) - p.stdin.close() - p.wait() - result = open(t.recv_out_path, 'rb').read() - expect = 'script_recv: ' + input - assert result == expect, repr(result) - - p = test.start(stdin=1) - input = 'send() input to the receive script\n' - p.send(input) - p.stdin.close() - p.wait() - result = open(t.recv_out_path, 'rb').read() - expect = 'script_recv: ' + input - assert result == expect, repr(result) - - finally: - os.chdir(t.orig_cwd) - - # TODO(sgk): figure out how to eliminate the race conditions here. - def __FLAKY__test_send_recv(self): - """Test the send_recv() method of objects returned by start()""" - - t = self.setup_run_scripts() - - # Everything before this prepared our "source directory." - # Now do the real test. - try: - - test = TestCmd.TestCmd(program = t.recv_script, - interpreter = 'python', - workdir = '', - subdir = 'script_subdir') - - def do_send_recv(p, input): - send, stdout, stderr = p.send_recv(input) - stdout = self.translate_newlines(stdout) - stderr = self.translate_newlines(stderr) - return send, stdout, stderr - - p = test.start(stdin=1) - input = 'input to the receive script\n' - send, stdout, stderr = do_send_recv(p, input) - # Buffering issues and a race condition prevent this from - # being completely deterministic, so check for both null - # output and the first write() on each stream. - assert stdout in ("", "script_recv: STDOUT\n"), stdout - assert stderr in ("", "script_recv: STDERR\n"), stderr - send, stdout, stderr = do_send_recv(p, input) - assert stdout in ("", "script_recv: STDOUT\n"), stdout - assert stderr in ("", "script_recv: STDERR\n"), stderr - p.stdin.close() - stdout = self.translate_newlines(p.recv()) - stderr = self.translate_newlines(p.recv_err()) - assert stdout in ("", "script_recv: STDOUT\n"), stdout - assert stderr in ("", "script_recv: STDERR\n"), stderr - p.wait() - stdout = self.translate_newlines(p.recv()) - stderr = self.translate_newlines(p.recv_err()) - expect_stdout = """\ -script_recv: STDOUT -script_recv: STDOUT: input to the receive script -script_recv: STDOUT: input to the receive script -""" - expect_stderr = """\ -script_recv: STDERR -script_recv: STDERR: input to the receive script -script_recv: STDERR: input to the receive script -""" - assert stdout == expect_stdout, stdout - assert stderr == expect_stderr, stderr - result = open(t.recv_out_path, 'rb').read() - expect = ('script_recv: ' + input) * 2 - assert result == expect, (result, stdout, stderr) - - finally: - os.chdir(t.orig_cwd) - - - -class stdin_TestCase(TestCmdTestCase): - def test_stdin(self): - """Test stdin()""" - run_env = TestCmd.TestCmd(workdir = '') - run_env.write('run', """from __future__ import print_function -import fileinput -for line in fileinput.input(): - print('Y'.join(line[:-1].split('X'))) -""") - run_env.write('input', "X on X this X line X\n") - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') - test.run(arguments = 'input') - assert test.stdout() == "Y on Y this Y line Y\n" - test.run(stdin = "X is X here X tooX\n") - assert test.stdout() == "Y is Y here Y tooY\n" - test.run(stdin = """X here X -X there X -""") - assert test.stdout() == "Y here Y\nY there Y\n" - test.run(stdin = ["X line X\n", "X another X\n"]) - assert test.stdout() == "Y line Y\nY another Y\n" - - - -class stdout_TestCase(TestCmdTestCase): - def test_stdout(self): - """Test stdout()""" - run_env = TestCmd.TestCmd(workdir = '') - run_env.write('run1', """import sys -sys.stdout.write("run1 STDOUT %s\\n" % sys.argv[1:]) -sys.stdout.write("run1 STDOUT second line\\n") -sys.stderr.write("run1 STDERR %s\\n" % sys.argv[1:]) -sys.stderr.write("run1 STDERR second line\\n") -""") - run_env.write('run2', """import sys -sys.stdout.write("run2 STDOUT %s\\n" % sys.argv[1:]) -sys.stdout.write("run2 STDOUT second line\\n") -sys.stderr.write("run2 STDERR %s\\n" % sys.argv[1:]) -sys.stderr.write("run2 STDERR second line\\n") -""") - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - test = TestCmd.TestCmd(interpreter = 'python', workdir = '') - try: - output = test.stdout() - except IndexError: - pass - else: - raise IndexError("got unexpected output:\n\t`%s'\n" % output) - test.program_set('run1') - test.run(arguments = 'foo bar') - test.program_set('run2') - test.run(arguments = 'snafu') - # XXX SHOULD TEST ABSOLUTE NUMBER AS WELL - output = test.stdout() - assert output == "run2 STDOUT ['snafu']\nrun2 STDOUT second line\n", output - output = test.stdout(run = -1) - assert output == "run1 STDOUT ['foo', 'bar']\nrun1 STDOUT second line\n", output - - - -class subdir_TestCase(TestCmdTestCase): - def test_subdir(self): - """Test subdir()""" - test = TestCmd.TestCmd(workdir = '', subdir = ['no', 'such', 'subdir']) - assert not os.path.exists(test.workpath('no')) - - test = TestCmd.TestCmd(workdir = '', subdir = 'foo') - assert test.subdir('bar') == 1 - assert test.subdir(['foo', 'succeed']) == 1 - if os.name != "nt": - os.chmod(test.workpath('foo'), 0o500) - assert test.subdir(['foo', 'fail']) == 0 - assert test.subdir(['sub', 'dir', 'ectory'], 'sub') == 1 - assert test.subdir('one', - UserList(['one', 'two']), - ['one', 'two', 'three']) == 3 - assert os.path.isdir(test.workpath('foo')) - assert os.path.isdir(test.workpath('bar')) - assert os.path.isdir(test.workpath('foo', 'succeed')) - if os.name != "nt": - assert not os.path.exists(test.workpath('foo', 'fail')) - assert os.path.isdir(test.workpath('sub')) - assert not os.path.exists(test.workpath('sub', 'dir')) - assert not os.path.exists(test.workpath('sub', 'dir', 'ectory')) - assert os.path.isdir(test.workpath('one', 'two', 'three')) - - - -class symlink_TestCase(TestCmdTestCase): - def test_symlink(self): - """Test symlink()""" - try: os.symlink - except AttributeError: return - - test = TestCmd.TestCmd(workdir = '', subdir = 'foo') - wdir_file1 = os.path.join(test.workdir, 'file1') - wdir_target1 = os.path.join(test.workdir, 'target1') - wdir_foo_file2 = os.path.join(test.workdir, 'foo', 'file2') - wdir_target2 = os.path.join(test.workdir, 'target2') - wdir_foo_target2 = os.path.join(test.workdir, 'foo', 'target2') - - test.symlink('target1', 'file1') - assert os.path.islink(wdir_file1) - assert not os.path.exists(wdir_file1) - open(wdir_target1, 'w').write("") - assert os.path.exists(wdir_file1) - - test.symlink('target2', ['foo', 'file2']) - assert os.path.islink(wdir_foo_file2) - assert not os.path.exists(wdir_foo_file2) - open(wdir_target2, 'w').write("") - assert not os.path.exists(wdir_foo_file2) - open(wdir_foo_target2, 'w').write("") - assert os.path.exists(wdir_foo_file2) - - - -class tempdir_TestCase(TestCmdTestCase): - def setUp(self): - TestCmdTestCase.setUp(self) - self._tempdir = tempfile.mktemp() - os.mkdir(self._tempdir) - os.chdir(self._tempdir) - - def tearDown(self): - TestCmdTestCase.tearDown(self) - os.rmdir(self._tempdir) - - def test_tempdir(self): - """Test tempdir()""" - test = TestCmd.TestCmd() - tdir1 = test.tempdir() - assert os.path.isdir(tdir1) - test.workdir_set(None) - test.cleanup() - assert not os.path.exists(tdir1) - - test = TestCmd.TestCmd() - tdir2 = test.tempdir('temp') - assert os.path.isdir(tdir2) - tdir3 = test.tempdir() - assert os.path.isdir(tdir3) - test.workdir_set(None) - test.cleanup() - assert not os.path.exists(tdir2) - assert not os.path.exists(tdir3) - - -timeout_script = """\ -import sys -import time -seconds = int(sys.argv[1]) -sys.stdout.write('sleeping %s\\n' % seconds) -sys.stdout.flush() -time.sleep(seconds) -sys.stdout.write('slept %s\\n' % seconds) -sys.stdout.flush() -sys.exit(0) -""" - -class timeout_TestCase(TestCmdTestCase): - def test_initialization(self): - """Test initialization timeout""" - test = TestCmd.TestCmd(workdir='', timeout=2) - test.write('sleep.py', timeout_script) - - test.run([sys.executable, test.workpath('sleep.py'), '4']) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 4\n', test.stdout() - - test.run([sys.executable, test.workpath('sleep.py'), '4']) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 4\n', test.stdout() - - def test_cancellation(self): - """Test timer cancellation after firing""" - test = TestCmd.TestCmd(workdir='', timeout=4) - test.write('sleep.py', timeout_script) - - test.run([sys.executable, test.workpath('sleep.py'), '6']) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 6\n', test.stdout() - - test.run([sys.executable, test.workpath('sleep.py'), '2']) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 2\nslept 2\n', test.stdout() - - test.run([sys.executable, test.workpath('sleep.py'), '6']) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 6\n', test.stdout() - - def test_run(self): - """Test run() timeout""" - test = TestCmd.TestCmd(workdir='', timeout=8) - test.write('sleep.py', timeout_script) - - test.run([sys.executable, test.workpath('sleep.py'), '2'], - timeout=4) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 2\nslept 2\n', test.stdout() - - test.run([sys.executable, test.workpath('sleep.py'), '6'], - timeout=4) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 6\n', test.stdout() - - def test_set_timeout(self): - """Test set_timeout()""" - test = TestCmd.TestCmd(workdir='', timeout=2) - test.write('sleep.py', timeout_script) - - test.run([sys.executable, test.workpath('sleep.py'), '4']) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 4\n', test.stdout() - - test.set_timeout(None) - - test.run([sys.executable, test.workpath('sleep.py'), '4']) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 4\nslept 4\n', test.stdout() - - test.set_timeout(6) - - test.run([sys.executable, test.workpath('sleep.py'), '4']) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 4\nslept 4\n', test.stdout() - - test.run([sys.executable, test.workpath('sleep.py'), '8']) - assert test.stderr() == '', test.stderr() - assert test.stdout() == 'sleeping 8\n', test.stdout() - - - -class unlink_TestCase(TestCmdTestCase): - def test_unlink(self): - """Test unlink()""" - test = TestCmd.TestCmd(workdir = '', subdir = 'foo') - wdir_file1 = os.path.join(test.workdir, 'file1') - wdir_file2 = os.path.join(test.workdir, 'file2') - wdir_foo_file3a = os.path.join(test.workdir, 'foo', 'file3a') - wdir_foo_file3b = os.path.join(test.workdir, 'foo', 'file3b') - wdir_foo_file4 = os.path.join(test.workdir, 'foo', 'file4') - wdir_file5 = os.path.join(test.workdir, 'file5') - - open(wdir_file1, 'w').write("") - open(wdir_file2, 'w').write("") - open(wdir_foo_file3a, 'w').write("") - open(wdir_foo_file3b, 'w').write("") - open(wdir_foo_file4, 'w').write("") - open(wdir_file5, 'w').write("") - - try: - contents = test.unlink('no_file') - except OSError: # expect "No such file or directory" - pass - except: - raise - - test.unlink("file1") - assert not os.path.exists(wdir_file1) - - test.unlink(wdir_file2) - assert not os.path.exists(wdir_file2) - - test.unlink(['foo', 'file3a']) - assert not os.path.exists(wdir_foo_file3a) - - test.unlink(UserList(['foo', 'file3b'])) - assert not os.path.exists(wdir_foo_file3b) - - test.unlink([test.workdir, 'foo', 'file4']) - assert not os.path.exists(wdir_foo_file4) - - # Make it so we can't unlink file5. - # For UNIX, remove write permission from the dir and the file. - # For Windows, open the file. - os.chmod(test.workdir, 0o500) - os.chmod(wdir_file5, 0o400) - f = open(wdir_file5, 'r') - - try: - try: - test.unlink('file5') - except OSError: # expect "Permission denied" - pass - except: - raise - finally: - os.chmod(test.workdir, 0o700) - os.chmod(wdir_file5, 0o600) - f.close() - - - -class touch_TestCase(TestCmdTestCase): - def test_touch(self): - """Test touch()""" - test = TestCmd.TestCmd(workdir = '', subdir = 'sub') - - wdir_file1 = os.path.join(test.workdir, 'file1') - wdir_sub_file2 = os.path.join(test.workdir, 'sub', 'file2') - - open(wdir_file1, 'w').write("") - open(wdir_sub_file2, 'w').write("") - - file1_old_time = os.path.getmtime(wdir_file1) - file2_old_time = os.path.getmtime(wdir_sub_file2) - - test.sleep() - - test.touch(wdir_file1) - - file1_new_time = os.path.getmtime(wdir_file1) - assert file1_new_time > file1_old_time - - test.touch('file1', file1_old_time) - - result = os.path.getmtime(wdir_file1) - # Sub-second granularity of file systems may still vary. - # On Windows, the two times may be off by a microsecond. - assert int(result) == int(file1_old_time), (result, file1_old_time) - - test.touch(['sub', 'file2']) - - file2_new_time = os.path.getmtime(wdir_sub_file2) - assert file2_new_time > file2_old_time - - - -class verbose_TestCase(TestCmdTestCase): - def test_verbose(self): - """Test verbose()""" - test = TestCmd.TestCmd() - assert test.verbose == 0, 'verbose already initialized?' - test = TestCmd.TestCmd(verbose = 1) - assert test.verbose == 1, 'did not initialize verbose' - test.verbose = 2 - assert test.verbose == 2, 'did not set verbose' - - - -class workdir_TestCase(TestCmdTestCase): - def test_workdir(self): - """Test workdir()""" - run_env = TestCmd.TestCmd(workdir = '') - os.chdir(run_env.workdir) - # Everything before this prepared our "source directory." - # Now do the real test. - test = TestCmd.TestCmd() - assert test.workdir is None - - test = TestCmd.TestCmd(workdir = None) - assert test.workdir is None - - test = TestCmd.TestCmd(workdir = '') - assert test.workdir != None - assert os.path.isdir(test.workdir) - - test = TestCmd.TestCmd(workdir = 'dir') - assert test.workdir != None - assert os.path.isdir(test.workdir) - - no_such_subdir = os.path.join('no', 'such', 'subdir') - try: - test = TestCmd.TestCmd(workdir = no_such_subdir) - except OSError: # expect "No such file or directory" - pass - except: - raise - - test = TestCmd.TestCmd(workdir = 'foo') - workdir_foo = test.workdir - assert workdir_foo != None - - test.workdir_set('bar') - workdir_bar = test.workdir - assert workdir_bar != None - - try: - test.workdir_set(no_such_subdir) - except OSError: - pass # expect "No such file or directory" - except: - raise - assert workdir_bar == test.workdir - - assert os.path.isdir(workdir_foo) - assert os.path.isdir(workdir_bar) - - - -class workdirs_TestCase(TestCmdTestCase): - def test_workdirs(self): - """Test workdirs()""" - test = TestCmd.TestCmd() - assert test.workdir is None - test.workdir_set('') - wdir1 = test.workdir - test.workdir_set('') - wdir2 = test.workdir - assert os.path.isdir(wdir1) - assert os.path.isdir(wdir2) - test.cleanup() - assert not os.path.exists(wdir1) - assert not os.path.exists(wdir2) - - - -class workpath_TestCase(TestCmdTestCase): - def test_workpath(self): - """Test workpath()""" - test = TestCmd.TestCmd() - assert test.workdir is None - - test = TestCmd.TestCmd(workdir = '') - wpath = test.workpath('foo', 'bar') - assert wpath == os.path.join(test.workdir, 'foo', 'bar') - - - -class readable_TestCase(TestCmdTestCase): - def test_readable(self): - """Test readable()""" - test = TestCmd.TestCmd(workdir = '', subdir = 'foo') - test.write('file1', "Test file #1\n") - test.write(['foo', 'file2'], "Test file #2\n") - - try: symlink = os.symlink - except AttributeError: pass - else: symlink('no_such_file', test.workpath('dangling_symlink')) - - test.readable(test.workdir, 0) - # XXX skip these tests if euid == 0? - assert not _is_readable(test.workdir) - assert not _is_readable(test.workpath('file1')) - assert not _is_readable(test.workpath('foo')) - assert not _is_readable(test.workpath('foo', 'file2')) - - test.readable(test.workdir, 1) - assert _is_readable(test.workdir) - assert _is_readable(test.workpath('file1')) - assert _is_readable(test.workpath('foo')) - assert _is_readable(test.workpath('foo', 'file2')) - - test.readable(test.workdir, 0) - # XXX skip these tests if euid == 0? - assert not _is_readable(test.workdir) - assert not _is_readable(test.workpath('file1')) - assert not _is_readable(test.workpath('foo')) - assert not _is_readable(test.workpath('foo', 'file2')) - - test.readable(test.workpath('file1'), 1) - assert _is_readable(test.workpath('file1')) - - test.readable(test.workpath('file1'), 0) - assert not _is_readable(test.workpath('file1')) - - test.readable(test.workdir, 1) - - - -class writable_TestCase(TestCmdTestCase): - def test_writable(self): - """Test writable()""" - test = TestCmd.TestCmd(workdir = '', subdir = 'foo') - test.write('file1', "Test file #1\n") - test.write(['foo', 'file2'], "Test file #2\n") - - try: symlink = os.symlink - except AttributeError: pass - else: symlink('no_such_file', test.workpath('dangling_symlink')) - - test.writable(test.workdir, 0) - # XXX skip these tests if euid == 0? - assert not _is_writable(test.workdir) - assert not _is_writable(test.workpath('file1')) - assert not _is_writable(test.workpath('foo')) - assert not _is_writable(test.workpath('foo', 'file2')) - - test.writable(test.workdir, 1) - assert _is_writable(test.workdir) - assert _is_writable(test.workpath('file1')) - assert _is_writable(test.workpath('foo')) - assert _is_writable(test.workpath('foo', 'file2')) - - test.writable(test.workdir, 0) - # XXX skip these tests if euid == 0? - assert not _is_writable(test.workdir) - assert not _is_writable(test.workpath('file1')) - assert not _is_writable(test.workpath('foo')) - assert not _is_writable(test.workpath('foo', 'file2')) - - test.writable(test.workpath('file1'), 1) - assert _is_writable(test.workpath('file1')) - - test.writable(test.workpath('file1'), 0) - assert not _is_writable(test.workpath('file1')) - - - -class executable_TestCase(TestCmdTestCase): - def test_executable(self): - """Test executable()""" - test = TestCmd.TestCmd(workdir = '', subdir = 'foo') - test.write('file1', "Test file #1\n") - test.write(['foo', 'file2'], "Test file #2\n") - - try: symlink = os.symlink - except AttributeError: pass - else: symlink('no_such_file', test.workpath('dangling_symlink')) - - def make_executable(fname): - st = os.stat(fname) - os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0o100)) - - def make_non_executable(fname): - st = os.stat(fname) - os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0o100)) - - test.executable(test.workdir, 0) - # XXX skip these tests if euid == 0? - assert not _is_executable(test.workdir) - make_executable(test.workdir) - assert not _is_executable(test.workpath('file1')) - assert not _is_executable(test.workpath('foo')) - make_executable(test.workpath('foo')) - assert not _is_executable(test.workpath('foo', 'file2')) - make_non_executable(test.workpath('foo')) - make_non_executable(test.workdir) - - test.executable(test.workdir, 1) - assert _is_executable(test.workdir) - assert _is_executable(test.workpath('file1')) - assert _is_executable(test.workpath('foo')) - assert _is_executable(test.workpath('foo', 'file2')) - - test.executable(test.workdir, 0) - # XXX skip these tests if euid == 0? - assert not _is_executable(test.workdir) - make_executable(test.workdir) - assert not _is_executable(test.workpath('file1')) - assert not _is_executable(test.workpath('foo')) - make_executable(test.workpath('foo')) - assert not _is_executable(test.workpath('foo', 'file2')) - - test.executable(test.workpath('file1'), 1) - assert _is_executable(test.workpath('file1')) - - test.executable(test.workpath('file1'), 0) - assert not _is_executable(test.workpath('file1')) - - test.executable(test.workdir, 1) - - - -class write_TestCase(TestCmdTestCase): - def test_write(self): - """Test write()""" - test = TestCmd.TestCmd(workdir = '', subdir = 'foo') - test.write('file1', "Test file #1\n") - test.write(['foo', 'file2'], "Test file #2\n") - try: - test.write(['bar', 'file3'], "Test file #3 (should not get created)\n") - except IOError: # expect "No such file or directory" - pass - except: - raise - test.write(test.workpath('file4'), "Test file #4.\n") - test.write(test.workpath('foo', 'file5'), "Test file #5.\n") - try: - test.write(test.workpath('bar', 'file6'), "Test file #6 (should not get created)\n") - except IOError: # expect "No such file or directory" - pass - except: - raise - - try: - test.write('file7', "Test file #8.\n", mode = 'r') - except ValueError: # expect "mode must begin with 'w' - pass - except: - raise - - test.write('file8', "Test file #8.\n", mode = 'w') - test.write('file9', "Test file #9.\r\n", mode = 'wb') - - if os.name != "nt": - os.chmod(test.workdir, 0o500) - try: - test.write('file10', "Test file #10 (should not get created).\n") - except IOError: # expect "Permission denied" - pass - except: - raise - - assert os.path.isdir(test.workpath('foo')) - assert not os.path.exists(test.workpath('bar')) - assert os.path.isfile(test.workpath('file1')) - assert os.path.isfile(test.workpath('foo', 'file2')) - assert not os.path.exists(test.workpath('bar', 'file3')) - assert os.path.isfile(test.workpath('file4')) - assert os.path.isfile(test.workpath('foo', 'file5')) - assert not os.path.exists(test.workpath('bar', 'file6')) - assert not os.path.exists(test.workpath('file7')) - assert os.path.isfile(test.workpath('file8')) - assert os.path.isfile(test.workpath('file9')) - if os.name != "nt": - assert not os.path.exists(test.workpath('file10')) - - assert open(test.workpath('file8'), 'r').read() == "Test file #8.\n" - assert open(test.workpath('file9'), 'rb').read() == "Test file #9.\r\n" - - - -class variables_TestCase(TestCmdTestCase): - def test_variables(self): - """Test global variables""" - run_env = TestCmd.TestCmd(workdir = '') - - variables = [ - 'fail_test', - 'no_result', - 'pass_test', - 'match_exact', - 'match_re', - 'match_re_dotall', - 'python', - '_python_', - 'TestCmd', - ] - - script = "from __future__ import print_function\n" + \ - "import TestCmd\n" + \ - '\n'.join([ "print(TestCmd.%s\n)" % v for v in variables ]) - run_env.run(program=sys.executable, stdin=script) - stderr = run_env.stderr() - assert stderr == "", stderr - - script = "from __future__ import print_function\n" + \ - "from TestCmd import *\n" + \ - '\n'.join([ "print(%s)" % v for v in variables ]) - run_env.run(program=sys.executable, stdin=script) - stderr = run_env.stderr() - assert stderr == "", stderr - - - -if __name__ == "__main__": - tclasses = [ - __init__TestCase, - basename_TestCase, - cleanup_TestCase, - chmod_TestCase, - combine_TestCase, - command_args_TestCase, - description_TestCase, - diff_TestCase, - diff_stderr_TestCase, - diff_stdout_TestCase, - exit_TestCase, - fail_test_TestCase, - interpreter_TestCase, - match_TestCase, - match_exact_TestCase, - match_re_dotall_TestCase, - match_re_TestCase, - match_stderr_TestCase, - match_stdout_TestCase, - no_result_TestCase, - pass_test_TestCase, - preserve_TestCase, - program_TestCase, - read_TestCase, - rmdir_TestCase, - run_TestCase, - run_verbose_TestCase, - set_diff_function_TestCase, - set_match_function_TestCase, - sleep_TestCase, - start_TestCase, - stderr_TestCase, - stdin_TestCase, - stdout_TestCase, - subdir_TestCase, - symlink_TestCase, - tempdir_TestCase, - timeout_TestCase, - unlink_TestCase, - touch_TestCase, - verbose_TestCase, - workdir_TestCase, - workdirs_TestCase, - workpath_TestCase, - writable_TestCase, - write_TestCase, - variables_TestCase, - ] - if sys.platform != 'win32': - tclasses.extend([ - executable_TestCase, - readable_TestCase, - ]) - suite = unittest.TestSuite() - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests([ tclass(n) for n in names ]) - if not unittest.TextTestRunner().run(suite).wasSuccessful(): - sys.exit(1) - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/QMTest/TestCommon.py b/QMTest/TestCommon.py deleted file mode 100644 index 47a149b..0000000 --- a/QMTest/TestCommon.py +++ /dev/null @@ -1,748 +0,0 @@ -""" -TestCommon.py: a testing framework for commands and scripts - with commonly useful error handling - -The TestCommon module provides a simple, high-level interface for writing -tests of executable commands and scripts, especially commands and scripts -that interact with the file system. All methods throw exceptions and -exit on failure, with useful error messages. This makes a number of -explicit checks unnecessary, making the test scripts themselves simpler -to write and easier to read. - -The TestCommon class is a subclass of the TestCmd class. In essence, -TestCommon is a wrapper that handles common TestCmd error conditions in -useful ways. You can use TestCommon directly, or subclass it for your -program and add additional (or override) methods to tailor it to your -program's specific needs. Alternatively, the TestCommon class serves -as a useful example of how to define your own TestCmd subclass. - -As a subclass of TestCmd, TestCommon provides access to all of the -variables and methods from the TestCmd module. Consequently, you can -use any variable or method documented in the TestCmd module without -having to explicitly import TestCmd. - -A TestCommon environment object is created via the usual invocation: - - import TestCommon - test = TestCommon.TestCommon() - -You can use all of the TestCmd keyword arguments when instantiating a -TestCommon object; see the TestCmd documentation for details. - -Here is an overview of the methods and keyword arguments that are -provided by the TestCommon class: - - test.must_be_writable('file1', ['file2', ...]) - - test.must_contain('file', 'required text\n') - - test.must_contain_all(output, input, ['title', find]) - - test.must_contain_all_lines(output, lines, ['title', find]) - - test.must_contain_any_line(output, lines, ['title', find]) - - test.must_contain_exactly_lines(output, lines, ['title', find]) - - test.must_exist('file1', ['file2', ...]) - - test.must_match('file', "expected contents\n") - - test.must_not_be_writable('file1', ['file2', ...]) - - test.must_not_contain('file', 'banned text\n') - - test.must_not_contain_any_line(output, lines, ['title', find]) - - test.must_not_exist('file1', ['file2', ...]) - - test.run(options = "options to be prepended to arguments", - stdout = "expected standard output from the program", - stderr = "expected error output from the program", - status = expected_status, - match = match_function) - -The TestCommon module also provides the following variables - - TestCommon.python - TestCommon._python_ - TestCommon.exe_suffix - TestCommon.obj_suffix - TestCommon.shobj_prefix - TestCommon.shobj_suffix - TestCommon.lib_prefix - TestCommon.lib_suffix - TestCommon.dll_prefix - TestCommon.dll_suffix - -""" - -# Copyright 2000-2010 Steven Knight -# This module is free software, and you may redistribute it and/or modify -# it under the same terms as Python itself, so long as this copyright message -# and disclaimer are retained in their original form. -# -# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -# THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. -# -# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -# AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - -from __future__ import print_function - -__author__ = "Steven Knight " -__revision__ = "TestCommon.py 1.3.D001 2010/06/03 12:58:27 knight" -__version__ = "1.3" - -import copy -import os -import stat -import sys -import glob - -try: - from collections import UserList -except ImportError: - # no 'collections' module or no UserList in collections - exec('from UserList import UserList') - -from TestCmd import * -from TestCmd import __all__ - -__all__.extend([ 'TestCommon', - 'exe_suffix', - 'obj_suffix', - 'shobj_prefix', - 'shobj_suffix', - 'lib_prefix', - 'lib_suffix', - 'dll_prefix', - 'dll_suffix', - ]) - -# Variables that describe the prefixes and suffixes on this system. -if sys.platform == 'win32': - exe_suffix = '.exe' - obj_suffix = '.obj' - shobj_suffix = '.obj' - shobj_prefix = '' - lib_prefix = '' - lib_suffix = '.lib' - dll_prefix = '' - dll_suffix = '.dll' -elif sys.platform == 'cygwin': - exe_suffix = '.exe' - obj_suffix = '.o' - shobj_suffix = '.os' - shobj_prefix = '' - lib_prefix = 'lib' - lib_suffix = '.a' - dll_prefix = 'cyg' - dll_suffix = '.dll' -elif sys.platform.find('irix') != -1: - exe_suffix = '' - obj_suffix = '.o' - shobj_suffix = '.o' - shobj_prefix = '' - lib_prefix = 'lib' - lib_suffix = '.a' - dll_prefix = 'lib' - dll_suffix = '.so' -elif sys.platform.find('darwin') != -1: - exe_suffix = '' - obj_suffix = '.o' - shobj_suffix = '.os' - shobj_prefix = '' - lib_prefix = 'lib' - lib_suffix = '.a' - dll_prefix = 'lib' - dll_suffix = '.dylib' -elif sys.platform.find('sunos') != -1: - exe_suffix = '' - obj_suffix = '.o' - shobj_suffix = '.o' - shobj_prefix = 'so_' - lib_prefix = 'lib' - lib_suffix = '.a' - dll_prefix = 'lib' - dll_suffix = '.so' -else: - exe_suffix = '' - obj_suffix = '.o' - shobj_suffix = '.os' - shobj_prefix = '' - lib_prefix = 'lib' - lib_suffix = '.a' - dll_prefix = 'lib' - dll_suffix = '.so' - -def is_List(e): - return isinstance(e, (list, UserList)) - -def is_Tuple(e): - return isinstance(e, tuple) - -def is_Sequence(e): - return (not hasattr(e, "strip") and - hasattr(e, "__getitem__") or - hasattr(e, "__iter__")) - -def is_writable(f): - mode = os.stat(f)[stat.ST_MODE] - return mode & stat.S_IWUSR - -def separate_files(flist): - existing = [] - missing = [] - for f in flist: - if os.path.exists(f): - existing.append(f) - else: - missing.append(f) - return existing, missing - -if os.name == 'posix': - def _failed(self, status = 0): - if self.status is None or status is None: - return None - return _status(self) != status - def _status(self): - return self.status -elif os.name == 'nt': - def _failed(self, status = 0): - return not (self.status is None or status is None) and \ - self.status != status - def _status(self): - return self.status - -class TestCommon(TestCmd): - - # Additional methods from the Perl Test::Cmd::Common module - # that we may wish to add in the future: - # - # $test->subdir('subdir', ...); - # - # $test->copy('src_file', 'dst_file'); - - def __init__(self, **kw): - """Initialize a new TestCommon instance. This involves just - calling the base class initialization, and then changing directory - to the workdir. - """ - TestCmd.__init__(self, **kw) - os.chdir(self.workdir) - - def options_arguments(self, options, arguments): - """Merges the "options" keyword argument with the arguments.""" - if options: - if arguments is None: - return options - if isinstance(options, str): - options = [options] - if isinstance(arguments, str): - arguments = [arguments] - arguments = ' '.join(options + arguments) - return arguments - - def must_be_writable(self, *files): - """Ensures that the specified file(s) exist and are writable. - An individual file can be specified as a list of directory names, - in which case the pathname will be constructed by concatenating - them. Exits FAILED if any of the files does not exist or is - not writable. - """ - files = [is_List(x) and os.path.join(*x) or x for x in files] - existing, missing = separate_files(files) - unwritable = [x for x in existing if not is_writable(x)] - if missing: - print("Missing files: `%s'" % "', `".join(missing)) - if unwritable: - print("Unwritable files: `%s'" % "', `".join(unwritable)) - self.fail_test(missing + unwritable) - - def must_contain(self, file, required, mode = 'rb', find = None): - """Ensures that the specified file contains the required text. - """ - file_contents = self.read(file, mode) - if find is None: - def find(o, l): - try: - return o.index(l) - except ValueError: - return None - contains = find(file_contents, required) - if not contains: - print("File `%s' does not contain required string." % file) - print(self.banner('Required string ')) - print(required) - print(self.banner('%s contents ' % file)) - print(file_contents) - self.fail_test(not contains) - - def must_contain_all(self, output, input, title=None, find=None): - """Ensures that the specified output string (first argument) - contains all of the specified input as a block (second argument). - - An optional third argument can be used to describe the type - of output being searched, and only shows up in failure output. - - An optional fourth argument can be used to supply a different - function, of the form "find(line, output), to use when searching - for lines in the output. - """ - if find is None: - def find(o, i): - try: - return o.index(i) - except ValueError: - return None - - if is_List(output): - output = os.newline.join(output) - - if find(output, input) is None: - if title is None: - title = 'output' - print('Missing expected input from {}:'.format(title)) - print(input) - print(self.banner(title + ' ')) - print(output) - self.fail_test() - - def must_contain_all_lines(self, output, lines, title=None, find=None): - """Ensures that the specified output string (first argument) - contains all of the specified lines (second argument). - - An optional third argument can be used to describe the type - of output being searched, and only shows up in failure output. - - An optional fourth argument can be used to supply a different - function, of the form "find(line, output), to use when searching - for lines in the output. - """ - if find is None: - def find(o, l): - try: - return o.index(l) - except ValueError: - return None - missing = [] - if is_List(output): - output = '\n'.join(output) - - for line in lines: - if find(output, line) is None: - missing.append(line) - - if missing: - if title is None: - title = 'output' - sys.stdout.write("Missing expected lines from %s:\n" % title) - for line in missing: - sys.stdout.write(' ' + repr(line) + '\n') - sys.stdout.write(self.banner(title + ' ') + '\n') - sys.stdout.write(output) - self.fail_test() - - def must_contain_any_line(self, output, lines, title=None, find=None): - """Ensures that the specified output string (first argument) - contains at least one of the specified lines (second argument). - - An optional third argument can be used to describe the type - of output being searched, and only shows up in failure output. - - An optional fourth argument can be used to supply a different - function, of the form "find(line, output), to use when searching - for lines in the output. - """ - if find is None: - def find(o, l): - try: - return o.index(l) - except ValueError: - return None - for line in lines: - if find(output, line) is not None: - return - - if title is None: - title = 'output' - sys.stdout.write("Missing any expected line from %s:\n" % title) - for line in lines: - sys.stdout.write(' ' + repr(line) + '\n') - sys.stdout.write(self.banner(title + ' ') + '\n') - sys.stdout.write(output) - self.fail_test() - - def must_contain_exactly_lines(self, output, expect, title=None, find=None): - """Ensures that the specified output string (first argument) - contains all of the lines in the expected string (second argument) - with none left over. - - An optional third argument can be used to describe the type - of output being searched, and only shows up in failure output. - - An optional fourth argument can be used to supply a different - function, of the form "find(line, output), to use when searching - for lines in the output. The function must return the index - of the found line in the output, or None if the line is not found. - """ - out = output.splitlines() - if is_List(expect): - exp = [ e.rstrip('\n') for e in expect ] - else: - exp = expect.splitlines() - if sorted(out) == sorted(exp): - # early out for exact match - return - if find is None: - def find(o, l): - try: - return o.index(l) - except ValueError: - return None - missing = [] - for line in exp: - found = find(out, line) - if found is None: - missing.append(line) - else: - out.pop(found) - - if not missing and not out: - # all lines were matched - return - - if title is None: - title = 'output' - if missing: - sys.stdout.write("Missing expected lines from %s:\n" % title) - for line in missing: - sys.stdout.write(' ' + repr(line) + '\n') - sys.stdout.write(self.banner('Missing %s ' % title) + '\n') - if out: - sys.stdout.write("Extra unexpected lines from %s:\n" % title) - for line in out: - sys.stdout.write(' ' + repr(line) + '\n') - sys.stdout.write(self.banner('Extra %s ' % title) + '\n') - sys.stdout.flush() - self.fail_test() - - def must_contain_lines(self, lines, output, title=None, find = None): - # Deprecated; retain for backwards compatibility. - return self.must_contain_all_lines(output, lines, title, find) - - def must_exist(self, *files): - """Ensures that the specified file(s) must exist. An individual - file be specified as a list of directory names, in which case the - pathname will be constructed by concatenating them. Exits FAILED - if any of the files does not exist. - """ - files = [is_List(x) and os.path.join(*x) or x for x in files] - missing = [x for x in files if not os.path.exists(x) and not os.path.islink(x) ] - if missing: - print("Missing files: `%s'" % "', `".join(missing)) - self.fail_test(missing) - - def must_exist_one_of(self, files): - """Ensures that at least one of the specified file(s) exists. - The filenames can be given as a list, where each entry may be - a single path string, or a tuple of folder names and the final - filename that get concatenated. - Supports wildcard names like 'foo-1.2.3-*.rpm'. - Exits FAILED if none of the files exists. - """ - missing = [] - for x in files: - if is_List(x) or is_Tuple(x): - xpath = os.path.join(*x) - else: - xpath = is_Sequence(x) and os.path.join(x) or x - if glob.glob(xpath): - return - missing.append(xpath) - print("Missing one of: `%s'" % "', `".join(missing)) - self.fail_test(missing) - - def must_match(self, file, expect, mode = 'rb', match=None, message=None, newline=None): - """Matches the contents of the specified file (first argument) - against the expected contents (second argument). The expected - contents are a list of lines or a string which will be split - on newlines. - """ - file_contents = self.read(file, mode, newline) - if not match: - match = self.match - try: - self.fail_test(not match(to_str(file_contents), to_str(expect)), message=message) - except KeyboardInterrupt: - raise - except: - print("Unexpected contents of `%s'" % file) - self.diff(expect, file_contents, 'contents ') - raise - - def must_not_contain(self, file, banned, mode = 'rb', find = None): - """Ensures that the specified file doesn't contain the banned text. - """ - file_contents = self.read(file, mode) - if find is None: - def find(o, l): - try: - return o.index(l) - except ValueError: - return None - contains = find(file_contents, banned) - if contains: - print("File `%s' contains banned string." % file) - print(self.banner('Banned string ')) - print(banned) - print(self.banner('%s contents ' % file)) - print(file_contents) - self.fail_test(contains) - - def must_not_contain_any_line(self, output, lines, title=None, find=None): - """Ensures that the specified output string (first argument) - does not contain any of the specified lines (second argument). - - An optional third argument can be used to describe the type - of output being searched, and only shows up in failure output. - - An optional fourth argument can be used to supply a different - function, of the form "find(line, output), to use when searching - for lines in the output. - """ - if find is None: - def find(o, l): - try: - return o.index(l) - except ValueError: - return None - unexpected = [] - for line in lines: - if find(output, line) is not None: - unexpected.append(line) - - if unexpected: - if title is None: - title = 'output' - sys.stdout.write("Unexpected lines in %s:\n" % title) - for line in unexpected: - sys.stdout.write(' ' + repr(line) + '\n') - sys.stdout.write(self.banner(title + ' ') + '\n') - sys.stdout.write(output) - self.fail_test() - - def must_not_contain_lines(self, lines, output, title=None, find=None): - return self.must_not_contain_any_line(output, lines, title, find) - - def must_not_exist(self, *files): - """Ensures that the specified file(s) must not exist. - An individual file be specified as a list of directory names, in - which case the pathname will be constructed by concatenating them. - Exits FAILED if any of the files exists. - """ - files = [is_List(x) and os.path.join(*x) or x for x in files] - existing = [x for x in files if os.path.exists(x) or os.path.islink(x)] - if existing: - print("Unexpected files exist: `%s'" % "', `".join(existing)) - self.fail_test(existing) - - def must_not_exist_any_of(self, files): - """Ensures that none of the specified file(s) exists. - The filenames can be given as a list, where each entry may be - a single path string, or a tuple of folder names and the final - filename that get concatenated. - Supports wildcard names like 'foo-1.2.3-*.rpm'. - Exits FAILED if any of the files exists. - """ - existing = [] - for x in files: - if is_List(x) or is_Tuple(x): - xpath = os.path.join(*x) - else: - xpath = is_Sequence(x) and os.path.join(x) or x - if glob.glob(xpath): - existing.append(xpath) - if existing: - print("Unexpected files exist: `%s'" % "', `".join(existing)) - self.fail_test(existing) - - def must_not_be_writable(self, *files): - """Ensures that the specified file(s) exist and are not writable. - An individual file can be specified as a list of directory names, - in which case the pathname will be constructed by concatenating - them. Exits FAILED if any of the files does not exist or is - writable. - """ - files = [is_List(x) and os.path.join(*x) or x for x in files] - existing, missing = separate_files(files) - writable = [file for file in existing if is_writable(file)] - if missing: - print("Missing files: `%s'" % "', `".join(missing)) - if writable: - print("Writable files: `%s'" % "', `".join(writable)) - self.fail_test(missing + writable) - - def _complete(self, actual_stdout, expected_stdout, - actual_stderr, expected_stderr, status, match): - """ - Post-processes running a subcommand, checking for failure - status and displaying output appropriately. - """ - if _failed(self, status): - expect = '' - if status != 0: - expect = " (expected %s)" % str(status) - print("%s returned %s%s" % (self.program, _status(self), expect)) - print(self.banner('STDOUT ')) - print(actual_stdout) - print(self.banner('STDERR ')) - print(actual_stderr) - self.fail_test() - if (expected_stdout is not None - and not match(actual_stdout, expected_stdout)): - self.diff(expected_stdout, actual_stdout, 'STDOUT ') - if actual_stderr: - print(self.banner('STDERR ')) - print(actual_stderr) - self.fail_test() - if (expected_stderr is not None - and not match(actual_stderr, expected_stderr)): - print(self.banner('STDOUT ')) - print(actual_stdout) - self.diff(expected_stderr, actual_stderr, 'STDERR ') - self.fail_test() - - def start(self, program = None, - interpreter = None, - options = None, - arguments = None, - universal_newlines = None, - **kw): - """ - Starts a program or script for the test environment, handling - any exceptions. - """ - arguments = self.options_arguments(options, arguments) - try: - return TestCmd.start(self, program, interpreter, arguments, - universal_newlines, **kw) - except KeyboardInterrupt: - raise - except Exception as e: - print(self.banner('STDOUT ')) - try: - print(self.stdout()) - except IndexError: - pass - print(self.banner('STDERR ')) - try: - print(self.stderr()) - except IndexError: - pass - cmd_args = self.command_args(program, interpreter, arguments) - sys.stderr.write('Exception trying to execute: %s\n' % cmd_args) - raise e - - def finish(self, popen, stdout = None, stderr = '', status = 0, **kw): - """ - Finishes and waits for the process being run under control of - the specified popen argument. Additional arguments are similar - to those of the run() method: - - stdout The expected standard output from - the command. A value of None means - don't test standard output. - - stderr The expected error output from - the command. A value of None means - don't test error output. - - status The expected exit status from the - command. A value of None means don't - test exit status. - """ - TestCmd.finish(self, popen, **kw) - match = kw.get('match', self.match) - self._complete(self.stdout(), stdout, - self.stderr(), stderr, status, match) - - def run(self, options = None, arguments = None, - stdout = None, stderr = '', status = 0, **kw): - """Runs the program under test, checking that the test succeeded. - - The parameters are the same as the base TestCmd.run() method, - with the addition of: - - options Extra options that get prepended to the beginning - of the arguments. - - stdout The expected standard output from - the command. A value of None means - don't test standard output. - - stderr The expected error output from - the command. A value of None means - don't test error output. - - status The expected exit status from the - command. A value of None means don't - test exit status. - - By default, this expects a successful exit (status = 0), does - not test standard output (stdout = None), and expects that error - output is empty (stderr = ""). - """ - kw['arguments'] = self.options_arguments(options, arguments) - try: - match = kw['match'] - del kw['match'] - except KeyError: - match = self.match - TestCmd.run(self, **kw) - self._complete(self.stdout(), stdout, - self.stderr(), stderr, status, match) - - def skip_test(self, message="Skipping test.\n"): - """Skips a test. - - Proper test-skipping behavior is dependent on the external - TESTCOMMON_PASS_SKIPS environment variable. If set, we treat - the skip as a PASS (exit 0), and otherwise treat it as NO RESULT. - In either case, we print the specified message as an indication - that the substance of the test was skipped. - - (This was originally added to support development under Aegis. - Technically, skipping a test is a NO RESULT, but Aegis would - treat that as a test failure and prevent the change from going to - the next step. Since we ddn't want to force anyone using Aegis - to have to install absolutely every tool used by the tests, we - would actually report to Aegis that a skipped test has PASSED - so that the workflow isn't held up.) - """ - if message: - sys.stdout.write(message) - sys.stdout.flush() - pass_skips = os.environ.get('TESTCOMMON_PASS_SKIPS') - if pass_skips in [None, 0, '0']: - # skip=1 means skip this function when showing where this - # result came from. They only care about the line where the - # script called test.skip_test(), not the line number where - # we call test.no_result(). - self.no_result(skip=1) - else: - # We're under the development directory for this change, - # so this is an Aegis invocation; pass the test (exit 0). - self.pass_test() - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/QMTest/TestCommonTests.py b/QMTest/TestCommonTests.py deleted file mode 100644 index 7949cb8..0000000 --- a/QMTest/TestCommonTests.py +++ /dev/null @@ -1,2340 +0,0 @@ -#!/usr/bin/env python -""" -TestCommonTests.py: Unit tests for the TestCommon.py module. - -Copyright 2000-2010 Steven Knight -This module is free software, and you may redistribute it and/or modify -it under the same terms as Python itself, so long as this copyright message -and disclaimer are retained in their original form. - -IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -""" - -__author__ = "Steven Knight " -__revision__ = "TestCommonTests.py 1.3.D001 2010/06/03 12:58:27 knight" - -import difflib -import os -import re -import signal -import stat -import sys -import unittest - -# Strip the current directory so we get the right TestCommon.py module. -sys.path = sys.path[1:] - -import TestCmd -import TestCommon - -def lstrip(s): - lines = [ _.expandtabs() for _ in s.split('\n') ] - if lines[0] == '': - lines = lines[1:] - spaces = len(re.match('^( *).*', lines[0]).group(1)) - if spaces: - lines = [ l[spaces:] for l in lines ] - return '\n'.join(lines) - -if sys.version[:3] == '1.5': - expected_newline = '\\012' -else: - expected_newline = '\\n' - -def assert_display(expect, result, error=None): - try: - expect = expect.pattern - except AttributeError: - pass - result = [ - '\n', - ('*'*80) + '\n', - expect, - ('*'*80) + '\n', - result, - ('*'*80) + '\n', - ] - if error: - result.append(error) - return ''.join(result) - - -class TestCommonTestCase(unittest.TestCase): - """Base class for TestCommon test cases, fixture and utility methods.""" - create_run_env = True - - def setUp(self): - self.orig_cwd = os.getcwd() - if self.create_run_env: - self.run_env = TestCmd.TestCmd(workdir = '') - - def tearDown(self): - os.chdir(self.orig_cwd) - - def set_up_execution_scripts(self): - run_env = self.run_env - - run_env.subdir('sub dir') - - self.python = sys.executable - - self.pass_script = run_env.workpath('sub dir', 'pass') - self.fail_script = run_env.workpath('sub dir', 'fail') - self.stdout_script = run_env.workpath('sub dir', 'stdout') - self.stderr_script = run_env.workpath('sub dir', 'stderr') - self.signal_script = run_env.workpath('sub dir', 'signal') - self.stdin_script = run_env.workpath('sub dir', 'stdin') - - preamble = "import sys" - stdout = "; sys.stdout.write(r'%s: STDOUT: ' + repr(sys.argv[1:]) + '\\n')" - stderr = "; sys.stderr.write(r'%s: STDERR: ' + repr(sys.argv[1:]) + '\\n')" - exit0 = "; sys.exit(0)" - exit1 = "; sys.exit(1)" - if sys.platform == 'win32': - wrapper = '@python -c "%s" %%1 %%2 %%3 %%4 %%5 %%6 %%7 %%8 %%9\n' - else: - wrapper = '#! /usr/bin/env python\n%s\n' - wrapper = '#! /usr/bin/env python\n%s\n' - - pass_body = preamble + stdout % self.pass_script + exit0 - fail_body = preamble + stdout % self.fail_script + exit1 - stderr_body = preamble + stderr % self.stderr_script + exit0 - - run_env.write(self.pass_script, wrapper % pass_body) - run_env.write(self.fail_script, wrapper % fail_body) - run_env.write(self.stderr_script, wrapper % stderr_body) - - signal_body = lstrip("""\ - import os - import signal - os.kill(os.getpid(), signal.SIGTERM) - """) - - run_env.write(self.signal_script, wrapper % signal_body) - - stdin_body = lstrip("""\ - import sys - input = sys.stdin.read()[:-1] - sys.stdout.write(r'%s: STDOUT: ' + repr(input) + '\\n') - sys.stderr.write(r'%s: STDERR: ' + repr(input) + '\\n') - """ % (self.stdin_script, self.stdin_script)) - - run_env.write(self.stdin_script, wrapper % stdin_body) - - def run_execution_test(self, script, expect_stdout, expect_stderr): - self.set_up_execution_scripts() - - run_env = self.run_env - - os.chdir(run_env.workpath('sub dir')) - - # Everything before this prepared our "source directory." - # Now do the real test. - script = script % self.__dict__ - run_env.run(program=sys.executable, stdin=script) - - stdout = run_env.stdout() - stderr = run_env.stderr() - - expect_stdout = expect_stdout % self.__dict__ - assert stdout == expect_stdout, assert_display(expect_stdout, - stdout, - stderr) - - try: - match = expect_stderr.match - except AttributeError: - expect_stderr = expect_stderr % self.__dict__ - assert stderr == expect_stderr, assert_display(expect_stderr, - stderr) - else: - assert expect_stderr.match(stderr), assert_display(expect_stderr, - stderr) - - -class __init__TestCase(TestCommonTestCase): - def test___init__(self): - """Test initialization""" - run_env = self.run_env - - os.chdir(run_env.workdir) - script = lstrip("""\ - from __future__ import print_function - from TestCommon import TestCommon - tc = TestCommon(workdir='') - import os - print(os.getcwd()) - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout()[:-1] - assert stdout != run_env.workdir, stdout - stderr = run_env.stderr() - assert stderr == "", stderr - - -class banner_TestCase(TestCommonTestCase): - create_run_env = False - def test_banner(self): - """Test banner()""" - tc = TestCommon.TestCommon(workdir='') - - b = tc.banner('xyzzy ') - assert b == "xyzzy ==========================================================================", b - - tc.banner_width = 10 - - b = tc.banner('xyzzy ') - assert b == "xyzzy ====", b - - b = tc.banner('xyzzy ', 20) - assert b == "xyzzy ==============", b - - tc.banner_char = '-' - - b = tc.banner('xyzzy ') - assert b == "xyzzy ----", b - -class must_be_writable_TestCase(TestCommonTestCase): - def test_file_does_not_exists(self): - """Test must_be_writable(): file does not exist""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.must_be_writable('file1') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Missing files: `file1'\n", stdout - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_writable_file_exists(self): - """Test must_be_writable(): writable file exists""" - run_env = self.run_env - - script = lstrip("""\ - import os - import stat - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n") - f1 = tc.workpath('file1') - mode = os.stat(f1)[stat.ST_MODE] - os.chmod(f1, mode | stat.S_IWUSR) - tc.must_be_writable('file1') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_non_writable_file_exists(self): - """Test must_be_writable(): non-writable file exists""" - run_env = self.run_env - - script = lstrip("""\ - import os - import stat - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n") - f1 = tc.workpath('file1') - mode = os.stat(f1)[stat.ST_MODE] - os.chmod(f1, mode & ~stat.S_IWUSR) - tc.must_be_writable('file1') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Unwritable files: `file1'\n", stdout - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_file_specified_as_list(self): - """Test must_be_writable(): file specified as list""" - run_env = self.run_env - - script = lstrip("""\ - import os - import stat - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.subdir('sub') - tc.write(['sub', 'file1'], "sub/file1\\n") - f1 = tc.workpath('sub', 'file1') - mode = os.stat(f1)[stat.ST_MODE] - os.chmod(f1, mode | stat.S_IWUSR) - tc.must_be_writable(['sub', 'file1']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - -class must_contain_TestCase(TestCommonTestCase): - def test_success(self): - """Test must_contain(): success""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1 contents\\n") - tc.must_contain('file1', "1 c") - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_file_missing(self): - """Test must_contain(): file missing""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.must_contain('file1', "1 c\\n") - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr.find("No such file or directory:") != -1, stderr - - def test_failure(self): - """Test must_contain(): failure""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1 does not match\\n") - tc.must_contain('file1', "1 c") - tc.run() - """) - expect = lstrip("""\ - File `file1' does not contain required string. - Required string ================================================================ - 1 c - file1 contents ================================================================= - file1 does not match - - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == expect, repr(stdout) - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_mode(self): - """Test must_contain(): mode""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1 contents\\n", mode='w') - tc.must_contain('file1', "1 c", mode='r') - tc.write('file2', "file2 contents\\n", mode='wb') - tc.must_contain('file2', "2 c", mode='rb') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - - -class must_contain_all_lines_TestCase(TestCommonTestCase): - def test_success(self): - """Test must_contain_all_lines(): success""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - xxx - yyy - zzz - ''' - - test.must_contain_all_lines(output, lines) - - test.must_contain_all_lines(output, ['www\\n']) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_failure(self): - """Test must_contain_all_lines(): failure""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - zzz - ''' - - test.must_contain_all_lines(output, lines) - - test.pass_test() - """) - - expect = lstrip("""\ - Missing expected lines from output: - 'xxx%(expected_newline)s' - 'yyy%(expected_newline)s' - output ========================================================================= - www - zzz - """ % globals()) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - stderr = run_env.stderr() - assert stdout == expect, assert_display(expect, stdout, stderr) - assert stderr.find("FAILED") != -1, stderr - - def test_find(self): - """Test must_contain_all_lines(): find""" - run_env = self.run_env - - script = lstrip(""" - import re - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'x.*', - '.*y', - ] - - output = '''\\ - www - xxx - yyy - zzz - ''' - - def re_search(output, line): - return re.compile(line, re.S).search(output) - test.must_contain_all_lines(output, lines, find=re_search) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_title(self): - """Test must_contain_all_lines(): title""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - zzz - ''' - - test.must_contain_all_lines(output, lines, title='STDERR') - - test.pass_test() - """) - - expect = lstrip("""\ - Missing expected lines from STDERR: - 'xxx%(expected_newline)s' - 'yyy%(expected_newline)s' - STDERR ========================================================================= - www - zzz - """ % globals()) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - stderr = run_env.stderr() - assert stdout == expect, assert_display(expect, stdout, stderr) - assert stderr.find("FAILED") != -1, stderr - - - -class must_contain_any_line_TestCase(TestCommonTestCase): - def test_success(self): - """Test must_contain_any_line(): success""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'aaa\\n', - 'yyy\\n', - ] - - output = '''\\ - www - xxx - yyy - zzz - ''' - - test.must_contain_any_line(output, lines) - - test.must_contain_any_line(output, ['www\\n']) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_failure(self): - """Test must_contain_any_line(): failure""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - zzz - ''' - - test.must_contain_any_line(output, lines) - - test.pass_test() - """) - - expect = lstrip("""\ - Missing any expected line from output: - 'xxx%(expected_newline)s' - 'yyy%(expected_newline)s' - output ========================================================================= - www - zzz - """ % globals()) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - stderr = run_env.stderr() - assert stdout == expect, assert_display(expect, stdout, stderr) - assert stderr.find("FAILED") != -1, stderr - - def test_find(self): - """Test must_contain_any_line(): find""" - run_env = self.run_env - - script = lstrip(""" - import re - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'aaa', - '.*y', - ] - - output = '''\\ - www - xxx - yyy - zzz - ''' - - def re_search(output, line): - return re.compile(line, re.S).search(output) - test.must_contain_any_line(output, lines, find=re_search) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_title(self): - """Test must_contain_any_line(): title""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - zzz - ''' - - test.must_contain_any_line(output, lines, title='STDOUT') - - test.pass_test() - """) - - expect = lstrip("""\ - Missing any expected line from STDOUT: - 'xxx%(expected_newline)s' - 'yyy%(expected_newline)s' - STDOUT ========================================================================= - www - zzz - """ % globals()) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - stderr = run_env.stderr() - assert stdout == expect, assert_display(expect, stdout, stderr) - assert stderr.find("FAILED") != -1, stderr - - - -class must_contain_exactly_lines_TestCase(TestCommonTestCase): - def test_success_list(self): - """Test must_contain_exactly_lines(): success (input list)""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'yyy\\n', - 'xxx\\n', - 'zzz', - 'www\\n', - ] - - output = '''\\ - www - xxx - yyy - zzz - ''' - - test.must_contain_exactly_lines(output, lines) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_success_string(self): - """Test must_contain_exactly_lines(): success (input string)""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = '''\\ - yyy - xxx - zzz - www - ''' - - output = '''\\ - www - xxx - yyy - zzz - ''' - - test.must_contain_exactly_lines(output, lines) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_failure(self): - """Test must_contain_exactly_lines(): failure""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - zzz - ''' - - test.must_contain_exactly_lines(output, lines) - - test.pass_test() - """) - - expect = lstrip("""\ - Missing expected lines from output: - 'xxx' - 'yyy' - Missing output ================================================================= - Extra unexpected lines from output: - 'www' - 'zzz' - Extra output =================================================================== - """ % globals()) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - stderr = run_env.stderr() - assert stdout == expect, assert_display(expect, stdout, stderr) - assert stderr.find("FAILED") != -1, stderr - - def test_find(self): - """Test must_contain_exactly_lines(): find""" - run_env = self.run_env - - script = lstrip(""" - import re - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'zzz', - '.*y', - 'xxx', - 'www', - ] - - output = '''\\\ - www - xxx - yyy - zzz - ''' - - def re_search(output, line): - pattern = re.compile(line, re.S) - index = 0 - for o in output: - if pattern.search(o): - return index - index +=1 - return None - test.must_contain_exactly_lines(output, lines, find=re_search) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_title(self): - """Test must_contain_exactly_lines(): title""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - zzz - ''' - - test.must_contain_exactly_lines(output, lines, title='STDOUT') - - test.pass_test() - """) - - expect = lstrip("""\ - Missing expected lines from STDOUT: - 'xxx' - 'yyy' - Missing STDOUT ================================================================= - Extra unexpected lines from STDOUT: - 'www' - 'zzz' - Extra STDOUT =================================================================== - """ % globals()) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - stderr = run_env.stderr() - assert stdout == expect, assert_display(expect, stdout, stderr) - assert stderr.find("FAILED") != -1, stderr - - - -class must_contain_lines_TestCase(TestCommonTestCase): - def test_success(self): - """Test must_contain_lines(): success""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - xxx - yyy - zzz - ''' - - test.must_contain_lines(lines, output) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_failure(self): - """Test must_contain_lines(): failure""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - zzz - ''' - - test.must_contain_lines(lines, output) - - test.pass_test() - """) - - expect = lstrip("""\ - Missing expected lines from output: - 'xxx%(expected_newline)s' - 'yyy%(expected_newline)s' - output ========================================================================= - www - zzz - """ % globals()) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - stderr = run_env.stderr() - assert stdout == expect, assert_display(expect, stdout, stderr) - assert stderr.find("FAILED") != -1, stderr - - - -class must_exist_TestCase(TestCommonTestCase): - def test_success(self): - """Test must_exist(): success""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n") - tc.must_exist('file1') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_failure(self): - """Test must_exist(): failure""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.must_exist('file1') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Missing files: `file1'\n", stdout - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_file_specified_as_list(self): - """Test must_exist(): file specified as list""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.subdir('sub') - tc.write(['sub', 'file1'], "sub/file1\\n") - tc.must_exist(['sub', 'file1']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_broken_link(self) : - """Test must_exist(): exists but it is a broken link""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.symlink('badtarget', "brokenlink") - tc.must_exist('brokenlink') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - -class must_exist_one_of_TestCase(TestCommonTestCase): - def test_success(self): - """Test must_exist_one_of(): success""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n") - tc.must_exist_one_of(['file1']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_failure(self): - """Test must_exist_one_of(): failure""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.must_exist_one_of(['file1']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Missing one of: `file1'\n", stdout - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_files_specified_as_list(self): - """Test must_exist_one_of(): files specified as list""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n") - tc.must_exist_one_of(['file2', 'file1']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_files_specified_with_wildcards(self): - """Test must_exist_one_of(): files specified with wildcards""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file7', "file7\\n") - tc.must_exist_one_of(['file?']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_file_given_as_list(self): - """Test must_exist_one_of(): file given as list""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.subdir('sub') - tc.write(['sub', 'file1'], "sub/file1\\n") - tc.must_exist_one_of(['file2', - ['sub', 'file1']]) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_file_given_as_sequence(self): - """Test must_exist_one_of(): file given as sequence""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.subdir('sub') - tc.write(['sub', 'file1'], "sub/file1\\n") - tc.must_exist_one_of(['file2', - ('sub', 'file1')]) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - -class must_match_TestCase(TestCommonTestCase): - def test_success(self): - """Test must_match(): success""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n") - tc.must_match('file1', "file1\\n") - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_file_does_not_exists(self): - """Test must_match(): file does not exist""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.must_match('file1', "file1\\n") - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr.find("No such file or directory:") != -1, stderr - - def test_failure(self): - """Test must_match(): failure""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1 does not match\\n") - tc.must_match('file1', "file1\\n") - tc.run() - """) - - expect = lstrip("""\ - Unexpected contents of `file1' - contents ======================================================================= - 1c1 - < file1 - --- - > file1 does not match - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == expect, stdout - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_mode(self): - """Test must_match(): mode""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n", mode='w') - tc.must_match('file1', "file1\\n", mode='r') - tc.write('file2', "file2\\n", mode='wb') - tc.must_match('file2', "file2\\n", mode='rb') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - - -class must_not_be_writable_TestCase(TestCommonTestCase): - def test_file_does_not_exists(self): - """Test must_not_be_writable(): file does not exist""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.must_not_be_writable('file1') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Missing files: `file1'\n", stdout - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_writable_file_exists(self): - """Test must_not_be_writable(): writable file exists""" - run_env = self.run_env - - script = lstrip("""\ - import os - import stat - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n") - f1 = tc.workpath('file1') - mode = os.stat(f1)[stat.ST_MODE] - os.chmod(f1, mode | stat.S_IWUSR) - tc.must_not_be_writable('file1') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Writable files: `file1'\n", stdout - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_non_writable_file_exists(self): - """Test must_not_be_writable(): non-writable file exists""" - run_env = self.run_env - - script = lstrip("""\ - import os - import stat - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n") - f1 = tc.workpath('file1') - mode = os.stat(f1)[stat.ST_MODE] - os.chmod(f1, mode & ~stat.S_IWUSR) - tc.must_not_be_writable('file1') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_file_specified_as_list(self): - """Test must_not_be_writable(): file specified as list""" - run_env = self.run_env - - script = lstrip("""\ - import os - import stat - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.subdir('sub') - tc.write(['sub', 'file1'], "sub/file1\\n") - f1 = tc.workpath('sub', 'file1') - mode = os.stat(f1)[stat.ST_MODE] - os.chmod(f1, mode & ~stat.S_IWUSR) - tc.must_not_be_writable(['sub', 'file1']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - - -class must_not_contain_TestCase(TestCommonTestCase): - def test_success(self): - """Test must_not_contain(): success""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1 contents\\n") - tc.must_not_contain('file1', "1 does not contain c") - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_file_does_not_exist(self): - """Test must_not_contain(): file does not exist""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.must_not_contain('file1', "1 c\\n") - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr.find("No such file or directory:") != -1, stderr - - def test_failure(self): - """Test must_not_contain(): failure""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1 does contain contents\\n") - tc.must_not_contain('file1', "1 does contain c") - tc.run() - """) - expect = lstrip("""\ - File `file1' contains banned string. - Banned string ================================================================== - 1 does contain c - file1 contents ================================================================= - file1 does contain contents - - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == expect, repr(stdout) - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_mode(self): - """Test must_not_contain(): mode""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1 contents\\n", mode='w') - tc.must_not_contain('file1', "1 does not contain c", mode='r') - tc.write('file2', "file2 contents\\n", mode='wb') - tc.must_not_contain('file2', "2 does not contain c", mode='rb') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - - -class must_not_contain_any_line_TestCase(TestCommonTestCase): - def test_failure(self): - """Test must_not_contain_any_line(): failure""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - 'www\\n', - ] - - output = '''\\ - www - xxx - yyy - zzz - ''' - - test.must_not_contain_any_line(output, lines) - - test.pass_test() - """) - - expect = lstrip("""\ - Unexpected lines in output: - 'xxx%(expected_newline)s' - 'yyy%(expected_newline)s' - 'www%(expected_newline)s' - output ========================================================================= - www - xxx - yyy - zzz - """ % globals()) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - stderr = run_env.stderr() - assert stdout == expect, assert_display(expect, stdout, stderr) - assert stderr.find("FAILED") != -1, stderr - - def test_find(self): - """Test must_not_contain_any_line(): find""" - run_env = self.run_env - - script = lstrip(""" - import re - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'x.*' - '.*y' - ] - - output = '''\\ - www - zzz - ''' - - def re_search(output, line): - return re.compile(line, re.S).search(output) - test.must_not_contain_any_line(output, lines, find=re_search) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_success(self): - """Test must_not_contain_any_line(): success""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n' - 'yyy\\n' - ] - - output = '''\\ - www - zzz - ''' - - test.must_not_contain_any_line(output, lines) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_title(self): - """Test must_not_contain_any_line(): title""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - xxx - yyy - zzz - ''' - - test.must_not_contain_any_line(output, lines, title='XYZZY') - - test.pass_test() - """) - - expect = lstrip("""\ - Unexpected lines in XYZZY: - 'xxx%(expected_newline)s' - 'yyy%(expected_newline)s' - XYZZY ========================================================================== - www - xxx - yyy - zzz - """ % globals()) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - stderr = run_env.stderr() - assert stdout == expect, assert_display(expect, stdout, stderr) - assert stderr.find("FAILED") != -1, stderr - - - -class must_not_contain_lines_TestCase(TestCommonTestCase): - def test_failure(self): - """Test must_not_contain_lines(): failure""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n', - 'yyy\\n', - ] - - output = '''\\ - www - xxx - yyy - zzz - ''' - - test.must_not_contain_lines(lines, output) - - test.pass_test() - """) - - expect = lstrip("""\ - Unexpected lines in output: - 'xxx%(expected_newline)s' - 'yyy%(expected_newline)s' - output ========================================================================= - www - xxx - yyy - zzz - """ % globals()) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - stderr = run_env.stderr() - assert stdout == expect, assert_display(expect, stdout, stderr) - assert stderr.find("FAILED") != -1, stderr - - def test_success(self): - """Test must_not_contain_lines(): success""" - run_env = self.run_env - - script = lstrip(""" - import TestCommon - test = TestCommon.TestCommon(workdir='') - - lines = [ - 'xxx\\n' - 'yyy\\n' - ] - - output = '''\\ - www - zzz - ''' - - test.must_not_contain_lines(lines, output) - - test.pass_test() - """) - - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - - -class must_not_exist_TestCase(TestCommonTestCase): - def test_failure(self): - """Test must_not_exist(): failure""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n") - tc.must_not_exist('file1') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Unexpected files exist: `file1'\n", stdout - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_success(self): - """Test must_not_exist(): success""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.must_not_exist('file1') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_file_specified_as_list(self): - """Test must_not_exist(): file specified as list""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.subdir('sub') - tc.must_not_exist(['sub', 'file1']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_existing_broken_link(self): - """Test must_not_exist(): exists but it is a broken link""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.symlink('badtarget', 'brokenlink') - tc.must_not_exist('brokenlink') - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Unexpected files exist: `brokenlink'\n", stdout - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - -class must_not_exist_any_of_TestCase(TestCommonTestCase): - def test_success(self): - """Test must_not_exist_any_of(): success""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.must_not_exist_any_of(['file1']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_failure(self): - """Test must_not_exist_any_of(): failure""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file1', "file1\\n") - tc.must_not_exist_any_of(['file1']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Unexpected files exist: `file1'\n", stdout - stderr = run_env.stderr() - assert stderr.find("FAILED") != -1, stderr - - def test_files_specified_as_list(self): - """Test must_not_exist_any_of(): files specified as list""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.must_not_exist_any_of(['file2', 'file1']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_files_specified_with_wildcards(self): - """Test must_not_exist_any_of(): files specified with wildcards""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.write('file7', "file7\\n") - tc.must_not_exist_any_of(['files?']) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_file_given_as_list(self): - """Test must_not_exist_any_of(): file given as list""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.subdir('sub') - tc.write(['sub', 'file1'], "sub/file1\\n") - tc.must_not_exist_any_of(['file2', - ['sub', 'files*']]) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - def test_file_given_as_sequence(self): - """Test must_not_exist_any_of(): file given as sequence""" - run_env = self.run_env - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(workdir='') - tc.subdir('sub') - tc.write(['sub', 'file1'], "sub/file1\\n") - tc.must_not_exist_any_of(['file2', - ('sub', 'files?')]) - tc.pass_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - -class run_TestCase(TestCommonTestCase): - def test_argument_handling(self): - """Test run(): argument handling""" - - script = lstrip("""\ - from TestCommon import TestCommon, match_exact - tc = TestCommon(program=r'%(pass_script)s', - interpreter='%(python)s', - workdir="", - match=match_exact) - tc.run(arguments = "arg1 arg2 arg3", - stdout = r"%(pass_script)s: STDOUT: ['arg1', 'arg2', 'arg3']" + "\\n") - """) - - self.run_execution_test(script, "", "") - - def test_default_pass(self): - """Test run(): default arguments, script passes""" - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(program=r'%(pass_script)s', - interpreter=r'%(python)s', - workdir='') - tc.run() - """) - - self.run_execution_test(script, "", "") - - def test_default_fail(self): - """Test run(): default arguments, script fails""" - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(program=r'%(fail_script)s', - interpreter='%(python)s', - workdir='') - tc.run() - """) - - expect_stdout = lstrip("""\ - %(fail_script)s returned 1 - STDOUT ========================================================================= - %(fail_script)s: STDOUT: [] - - STDERR ========================================================================= - - """) - - expect_stderr = lstrip("""\ - FAILED test of .*fail - \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) - \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) - \\tfrom line \\d+ of ( \(\))? - """) - expect_stderr = re.compile(expect_stderr, re.M) - - self.run_execution_test(script, expect_stdout, expect_stderr) - - def test_default_stderr(self): - """Test run(): default arguments, error output""" - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(program=r'%(stderr_script)s', - interpreter='%(python)s', - workdir='') - tc.run() - """) - - expect_stdout = lstrip("""\ - STDOUT ========================================================================= - - STDERR ========================================================================= - 0a1 - > %(stderr_script)s: STDERR: [] - """) - - expect_stderr = lstrip("""\ - FAILED test of .*stderr - \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) - \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) - \\tfrom line \\d+ of - """) - expect_stderr = re.compile(expect_stderr, re.M) - - self.run_execution_test(script, expect_stdout, expect_stderr) - - def test_exception_handling(self): - """Test run(): exception handling""" - script = lstrip("""\ - import TestCmd - from TestCommon import TestCommon - def raise_exception(*args, **kw): - raise TypeError("forced TypeError") - TestCmd.TestCmd.start = raise_exception - tc = TestCommon(program='%(pass_script)s', - interpreter='%(python)s', - workdir='') - tc.run() - """) - - expect_stdout = lstrip("""\ - STDOUT ========================================================================= - STDERR ========================================================================= - """) - - expect_stderr = lstrip("""\ - Exception trying to execute: \\[%s, '[^']*pass'\\] - Traceback \\((innermost|most recent call) last\\): - File "", line \\d+, in (\\?|) - File "[^"]+TestCommon.py", line \\d+, in run - TestCmd.run\\(self, \\*\\*kw\\) - File "[^"]+TestCmd.py", line \\d+, in run - .* - File "[^"]+TestCommon.py", line \\d+, in start - raise e - TypeError: forced TypeError - """ % re.escape(repr(sys.executable))) - expect_stderr = re.compile(expect_stderr, re.M) - - self.run_execution_test(script, expect_stdout, expect_stderr) - - def test_ignore_stderr(self): - """Test run(): ignore stderr""" - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(program=r'%(stderr_script)s', - interpreter='%(python)s', - workdir='') - tc.run(stderr = None) - """) - - self.run_execution_test(script, "", "") - - def test_match_function_stdout(self): - """Test run(): explicit match function, stdout""" - - script = lstrip("""\ - def my_match_exact(actual, expect): return actual == expect - from TestCommon import TestCommon, match_re_dotall - tc = TestCommon(program=r'%(pass_script)s', - interpreter='%(python)s', - workdir="", - match=match_re_dotall) - tc.run(arguments = "arg1 arg2 arg3", - stdout = r"%(pass_script)s: STDOUT: ['arg1', 'arg2', 'arg3']" + "\\n", - match = my_match_exact) - """) - - self.run_execution_test(script, "", "") - - def test_match_function_stderr(self): - """Test run(): explicit match function, stderr""" - - script = lstrip("""\ - def my_match_exact(actual, expect): return actual == expect - from TestCommon import TestCommon, match_re_dotall - tc = TestCommon(program=r'%(stderr_script)s', - interpreter='%(python)s', - workdir="", - match=match_re_dotall) - tc.run(arguments = "arg1 arg2 arg3", - stderr = r"%(stderr_script)s: STDERR: ['arg1', 'arg2', 'arg3']" + "\\n", - match = my_match_exact) - """) - - self.run_execution_test(script, "", "") - - def test_matched_status_fails(self): - """Test run(): matched status, script fails""" - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(program=r'%(fail_script)s', - interpreter='%(python)s', - workdir='') - tc.run(status = 1) - """) - - self.run_execution_test(script, "", "") - - def test_matched_stdout(self): - """Test run(): matched stdout""" - - script = lstrip("""\ - from TestCommon import TestCommon, match_exact - tc = TestCommon(program=r'%(pass_script)s', - interpreter='%(python)s', - workdir="", - match=match_exact) - tc.run(stdout = r"%(pass_script)s: STDOUT: []" + "\\n") - """) - - self.run_execution_test(script, "", "") - - def test_matched_stderr(self): - """Test run(): matched stderr""" - - script = lstrip("""\ - from TestCommon import TestCommon, match_exact - tc = TestCommon(program=r'%(stderr_script)s', - interpreter='%(python)s', - workdir="", - match=match_exact) - tc.run(stderr = r"%(stderr_script)s: STDERR: []" + "\\n") - """) - - self.run_execution_test(script, "", "") - - def test_mismatched_status_pass(self): - """Test run(): mismatched status, script passes""" - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(program=r'%(pass_script)s', - interpreter='%(python)s', - workdir='') - tc.run(status = 1) - """) - - expect_stdout = lstrip("""\ - %(pass_script)s returned 0 (expected 1) - STDOUT ========================================================================= - %(pass_script)s: STDOUT: [] - - STDERR ========================================================================= - - """) - - expect_stderr = lstrip("""\ - FAILED test of .*pass - \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) - \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) - \\tfrom line \\d+ of ( \(\))? - """) - expect_stderr = re.compile(expect_stderr, re.M) - - self.run_execution_test(script, expect_stdout, expect_stderr) - - def test_mismatched_status_fail(self): - """Test run(): mismatched status, script fails""" - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(program=r'%(fail_script)s', - interpreter='%(python)s', - workdir='') - tc.run(status = 2) - """) - - expect_stdout = lstrip("""\ - %(fail_script)s returned 1 (expected 2) - STDOUT ========================================================================= - %(fail_script)s: STDOUT: [] - - STDERR ========================================================================= - - """) - - expect_stderr = lstrip("""\ - FAILED test of .*fail - \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) - \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) - \\tfrom line \\d+ of ( \(\))? - """) - expect_stderr = re.compile(expect_stderr, re.M) - - self.run_execution_test(script, expect_stdout, expect_stderr) - - def test_mismatched_stdout(self): - """Test run(): mismatched stdout""" - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(program=r'%(pass_script)s', - interpreter='%(python)s', - workdir='') - tc.run(stdout = "Not found\\n") - """) - - expect_stdout = lstrip("""\ - STDOUT ========================================================================= - 1c1 - < Not found - --- - > %(pass_script)s: STDOUT: [] - """) - - expect_stderr = lstrip("""\ - FAILED test of .*pass - \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) - \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) - \\tfrom line \\d+ of ( \(\))? - """) - expect_stderr = re.compile(expect_stderr, re.M) - - self.run_execution_test(script, expect_stdout, expect_stderr) - - def test_mismatched_stderr(self): - """Test run(): mismatched stderr""" - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(program=r'%(stderr_script)s', - interpreter='%(python)s', - workdir='') - tc.run(stderr = "Not found\\n") - """) - - expect_stdout = lstrip("""\ - STDOUT ========================================================================= - - STDERR ========================================================================= - 1c1 - < Not found - --- - > %(stderr_script)s: STDERR: [] - """) - - expect_stderr = lstrip("""\ - FAILED test of .*stderr - \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) - \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) - \\tfrom line \\d+ of ( \(\))? - """) - expect_stderr = re.compile(expect_stderr, re.M) - - self.run_execution_test(script, expect_stdout, expect_stderr) - - def test_option_handling(self): - """Test run(): option handling""" - - script = lstrip("""\ - from TestCommon import TestCommon, match_exact - tc = TestCommon(program=r'%(pass_script)s', - interpreter='%(python)s', - workdir="", - match=match_exact) - tc.run(options = "opt1 opt2 opt3", - stdout = r"%(pass_script)s: STDOUT: ['opt1', 'opt2', 'opt3']" + "\\n") - """) - - self.run_execution_test(script, "", "") - - def test_options_plus_arguments(self): - """Test run(): option handling with arguments""" - - script = lstrip("""\ - from TestCommon import TestCommon, match_exact - tc = TestCommon(program=r'%(pass_script)s', - interpreter='%(python)s', - workdir="", - match=match_exact) - tc.run(options = "opt1 opt2 opt3", - arguments = "arg1 arg2 arg3", - stdout = r"%(pass_script)s: STDOUT: ['opt1', 'opt2', 'opt3', 'arg1', 'arg2', 'arg3']" + "\\n") - """) - - self.run_execution_test(script, "", "") - - def test_signal_handling(self): - """Test run(): signal handling""" - - try: - os.kill - except AttributeError: - sys.stderr.write('can not test, no os.kill ... ') - return - - script = lstrip("""\ - from TestCommon import TestCommon - tc = TestCommon(program=r'%(signal_script)s', - interpreter='%(python)s', - workdir='') - tc.run() - """) - - self.SIGTERM = signal.SIGTERM - - # Script returns the signal value as a negative number. - expect_stdout = lstrip("""\ - %(signal_script)s returned -%(SIGTERM)s - STDOUT ========================================================================= - - STDERR ========================================================================= - - """) - - expect_stderr = lstrip("""\ - FAILED test of .*signal - \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) - \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) - \\tfrom line \\d+ of - """) - expect_stderr = re.compile(expect_stderr, re.M) - - self.run_execution_test(script, expect_stdout, expect_stderr) - - def test_stdin(self): - """Test run(): stdin handling""" - - script = lstrip("""\ - from TestCommon import TestCommon, match_exact - tc = TestCommon(program=r'%(stdin_script)s', - interpreter='%(python)s', - workdir='', - match=match_exact) - expect_stdout = r"%(stdin_script)s: STDOUT: 'input'" + "\\n" - expect_stderr = r"%(stdin_script)s: STDERR: 'input'" + "\\n" - tc.run(stdin="input\\n", stdout = expect_stdout, stderr = expect_stderr) - """) - - expect_stdout = lstrip("""\ - %(pass_script)s returned 0 (expected 1) - STDOUT ========================================================================= - %(pass_script)s: STDOUT: [] - - STDERR ========================================================================= - - """) - - self.run_execution_test(script, "", "") - - - -class start_TestCase(TestCommonTestCase): - def test_option_handling(self): - """Test start(): option handling""" - - script = lstrip("""\ - from TestCommon import TestCommon, match_exact - tc = TestCommon(program=r'%(pass_script)s', - interpreter='%(python)s', - workdir="", - match=match_exact) - p = tc.start(options = "opt1 opt2 opt3") - expect = r"%(pass_script)s: STDOUT: ['opt1', 'opt2', 'opt3']" + "\\n" - tc.finish(p, stdout = expect) - """) - - self.run_execution_test(script, "", "") - - def test_options_plus_arguments(self): - """Test start(): option handling with arguments""" - - script = lstrip("""\ - from TestCommon import TestCommon, match_exact - tc = TestCommon(program=r'%(pass_script)s', - interpreter='%(python)s', - workdir="", - match=match_exact) - p = tc.start(options = "opt1 opt2 opt3", - arguments = "arg1 arg2 arg3") - expect = r"%(pass_script)s: STDOUT: ['opt1', 'opt2', 'opt3', 'arg1', 'arg2', 'arg3']" + "\\n" - tc.finish(p, stdout = expect) - """) - - self.run_execution_test(script, "", "") - - - -class skip_test_TestCase(TestCommonTestCase): - def test_skip_test(self): - """Test skip_test()""" - run_env = self.run_env - - script = lstrip("""\ - import TestCommon - test = TestCommon.TestCommon(workdir='') - test.skip_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Skipping test.\n", stdout - stderr = run_env.stderr() - expect = [ - "NO RESULT for test at line 3 of \n", - "NO RESULT for test at line 3 of ()\n", - ] - assert stderr in expect, repr(stderr) - - script = lstrip("""\ - import TestCommon - test = TestCommon.TestCommon(workdir='') - test.skip_test("skipping test because I said so\\n") - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "skipping test because I said so\n", stdout - stderr = run_env.stderr() - expect = [ - "NO RESULT for test at line 3 of \n", - "NO RESULT for test at line 3 of ()\n", - ] - assert stderr in expect, repr(stderr) - - import os - os.environ['TESTCOMMON_PASS_SKIPS'] = '1' - - try: - script = lstrip("""\ - import TestCommon - test = TestCommon.TestCommon(workdir='') - test.skip_test() - """) - run_env.run(program=sys.executable, stdin=script) - stdout = run_env.stdout() - assert stdout == "Skipping test.\n", stdout - stderr = run_env.stderr() - assert stderr == "PASSED\n", stderr - - finally: - del os.environ['TESTCOMMON_PASS_SKIPS'] - - - -class variables_TestCase(TestCommonTestCase): - def test_variables(self): - """Test global variables""" - run_env = self.run_env - - variables = [ - 'fail_test', - 'no_result', - 'pass_test', - 'match_exact', - 'match_re', - 'match_re_dotall', - 'python', - '_python_', - 'TestCmd', - - 'TestCommon', - 'exe_suffix', - 'obj_suffix', - 'shobj_prefix', - 'shobj_suffix', - 'lib_prefix', - 'lib_suffix', - 'dll_prefix', - 'dll_suffix', - ] - - script = "from __future__ import print_function" + \ - "import TestCommon\n" + \ - '\n'.join([ "print(TestCommon.%s)\n" % v for v in variables ]) - run_env.run(program=sys.executable, stdin=script) - stderr = run_env.stderr() - assert stderr == "", stderr - - script = "from __future__ import print_function" + \ - "from TestCommon import *\n" + \ - '\n'.join([ "print(%s)" % v for v in variables ]) - run_env.run(program=sys.executable, stdin=script) - stderr = run_env.stderr() - assert stderr == "", stderr - - - -if __name__ == "__main__": - tclasses = [ - __init__TestCase, - banner_TestCase, - must_be_writable_TestCase, - must_contain_TestCase, - must_contain_all_lines_TestCase, - must_contain_any_line_TestCase, - must_contain_exactly_lines_TestCase, - must_contain_lines_TestCase, - must_exist_TestCase, - must_exist_one_of_TestCase, - must_match_TestCase, - must_not_be_writable_TestCase, - must_not_contain_TestCase, - must_not_contain_any_line_TestCase, - must_not_contain_lines_TestCase, - must_not_exist_TestCase, - must_not_exist_any_of_TestCase, - run_TestCase, - start_TestCase, - skip_test_TestCase, - variables_TestCase, - ] - suite = unittest.TestSuite() - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests([ tclass(n) for n in names ]) - if not unittest.TextTestRunner().run(suite).wasSuccessful(): - sys.exit(1) - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/QMTest/TestRuntest.py b/QMTest/TestRuntest.py deleted file mode 100644 index 68563da..0000000 --- a/QMTest/TestRuntest.py +++ /dev/null @@ -1,167 +0,0 @@ -""" -TestRuntest.py: a testing framework for the runtest.py command used to -invoke SCons tests. - -A TestRuntest environment object is created via the usual invocation: - - test = TestRuntest() - -TestRuntest is a subclass of TestCommon, which is in turn is a subclass -of TestCmd), and hence has available all of the methods and attributes -from those classes, as well as any overridden or additional methods or -attributes defined in this subclass. -""" - -# __COPYRIGHT__ - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - -import os -import os.path -import re -import shutil -import sys - -from TestCommon import * -from TestCommon import __all__ - -__all__.extend([ 'TestRuntest', - 'pythonstring', - ]) - -if re.search('\s', python): - pythonstring = _python_ -else: - pythonstring = python -pythonstring = pythonstring.replace('\\', '\\\\') - - -failing_test_template = """\ -import sys -sys.stdout.write('FAILING TEST STDOUT\\n') -sys.stderr.write('FAILING TEST STDERR\\n') -sys.exit(1) -""" - -no_result_test_template = """\ -import sys -sys.stdout.write('NO RESULT TEST STDOUT\\n') -sys.stderr.write('NO RESULT TEST STDERR\\n') -sys.exit(2) -""" - -passing_test_template = """\ -import sys -sys.stdout.write('PASSING TEST STDOUT\\n') -sys.stderr.write('PASSING TEST STDERR\\n') -sys.exit(0) -""" - -fake_scons_py = """ -__version__ = '1.2.3' -__build__ = 'D123' -__buildsys__ = 'fake_system' -__date__ = 'Jan 1 1970' -__developer__ = 'Anonymous' -""" - -fake___init___py = """ -__version__ = '4.5.6' -__build__ = 'D456' -__buildsys__ = 'another_fake_system' -__date__ = 'Dec 31 1999' -__developer__ = 'John Doe' -""" - -class TestRuntest(TestCommon): - """Class for testing the runtest.py script. - - This provides a common place for initializing Runtest tests, - eliminating the need to begin every test with the same repeated - initializations. - """ - - def __init__(self, **kw): - """Initialize a Runtest testing object. - - If they're not overridden by keyword arguments, this - initializes the object with the following default values: - - program = 'runtest.py' - interpreter = ['python', '-tt'] - match = match_exact - workdir = '' - - The workdir value means that, by default, a temporary - workspace directory is created for a TestRuntest environment. - The superclass TestCommon.__init__() will change directory (chdir) - to the workspace directory, so an explicit "chdir = '.'" on all - of the run() method calls is not necessary. This initialization - also copies the runtest.py and QMTest/ subdirectory tree to the - temporary directory, duplicating how this test infrastructure - appears in a normal workspace. - """ - if 'program' not in kw: - kw['program'] = 'runtest.py' - if 'interpreter' not in kw: - kw['interpreter'] = [python, '-tt'] - if 'match' not in kw: - kw['match'] = match_exact - if 'workdir' not in kw: - kw['workdir'] = '' - - try: - things_to_copy = kw['things_to_copy'] - except KeyError: - things_to_copy = [ - 'runtest.py', - 'QMTest', - ] - else: - del kw['things_to_copy'] - - orig_cwd = os.getcwd() - TestCommon.__init__(self, **kw) - - dirs = [os.environ.get('SCONS_RUNTEST_DIR', orig_cwd)] - - for thing in things_to_copy: - for dir in dirs: - t = os.path.join(dir, thing) - if os.path.exists(t): - if os.path.isdir(t): - copy_func = shutil.copytree - else: - copy_func = shutil.copyfile - copy_func(t, self.workpath(thing)) - break - - self.program_set(self.workpath(kw['program'])) - - os.environ['PYTHONPATH'] = '' - - def write_fake_scons_source_tree(self): - os.mkdir('src') - os.mkdir('src/script') - self.write('src/script/scons.py', fake_scons_py) - - os.mkdir('src/engine') - os.mkdir('src/engine/SCons') - self.write('src/engine/SCons/__init__.py', fake___init___py) - os.mkdir('src/engine/SCons/Script') - self.write('src/engine/SCons/Script/__init__.py', fake___init___py) - - def write_failing_test(self, name): - self.write(name, failing_test_template) - - def write_no_result_test(self, name): - self.write(name, no_result_test_template) - - def write_passing_test(self, name): - self.write(name, passing_test_template) - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py deleted file mode 100644 index ef907d8..0000000 --- a/QMTest/TestSCons.py +++ /dev/null @@ -1,1642 +0,0 @@ -""" -TestSCons.py: a testing framework for the SCons software construction -tool. - -A TestSCons environment object is created via the usual invocation: - - test = TestSCons() - -TestScons is a subclass of TestCommon, which in turn is a subclass -of TestCmd), and hence has available all of the methods and attributes -from those classes, as well as any overridden or additional methods or -attributes defined in this subclass. -""" - -# __COPYRIGHT__ -from __future__ import division, print_function - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - -import os -import re -import shutil -import sys -import time -import subprocess - -from TestCommon import * -from TestCommon import __all__ - -from TestCmd import Popen -from TestCmd import PIPE - -# Some tests which verify that SCons has been packaged properly need to -# look for specific version file names. Replicating the version number -# here provides some independent verification that what we packaged -# conforms to what we expect. - -default_version = '3.1.0.alpha.yyyymmdd' - -python_version_unsupported = (2, 6, 0) -python_version_deprecated = (2, 7, 0) - -# In the checked-in source, the value of SConsVersion in the following -# line must remain "__ VERSION __" (without the spaces) so the built -# version in build/QMTest/TestSCons.py contains the actual version -# string of the packages that have been built. -SConsVersion = '__VERSION__' -if SConsVersion == '__' + 'VERSION' + '__': - SConsVersion = default_version - -__all__.extend([ - 'TestSCons', - 'machine', - 'python', - '_exe', - '_obj', - '_shobj', - 'shobj_', - 'lib_', - '_lib', - 'dll_', - '_dll' - ]) - -machine_map = { - 'i686' : 'i386', - 'i586' : 'i386', - 'i486' : 'i386', -} - -try: - uname = os.uname -except AttributeError: - # Windows doesn't have a uname() function. We could use something like - # sys.platform as a fallback, but that's not really a "machine," so - # just leave it as None. - machine = None -else: - machine = uname()[4] - machine = machine_map.get(machine, machine) - -_exe = exe_suffix -_obj = obj_suffix -_shobj = shobj_suffix -shobj_ = shobj_prefix -_lib = lib_suffix -lib_ = lib_prefix -_dll = dll_suffix -dll_ = dll_prefix - - -if sys.platform == 'cygwin': - # On Cygwin, os.path.normcase() lies, so just report back the - # fact that the underlying Win32 OS is case-insensitive. - def case_sensitive_suffixes(s1, s2): - return 0 -else: - def case_sensitive_suffixes(s1, s2): - return (os.path.normcase(s1) != os.path.normcase(s2)) - - -file_expr = r"""File "[^"]*", line \d+, in [^\n]+ -""" - -# re.escape escapes too much. -def re_escape(str): - for c in '\\.[]()*+?': # Not an exhaustive list. - str = str.replace(c, '\\' + c) - return str - -# -# Helper functions that we use as a replacement to the default re.match -# when searching for special strings in stdout/stderr. -# -def search_re(out, l): - """ Search the regular expression 'l' in the output 'out' - and return the start index when successful. - """ - m = re.search(l, out) - if m: - return m.start() - - return None - -def search_re_in_list(out, l): - """ Search the regular expression 'l' in each line of - the given string list 'out' and return the line's index - when successful. - """ - for idx, o in enumerate(out): - m = re.search(l, o) - if m: - return idx - - return None - -# -# Helpers for handling Python version numbers -# -def python_version_string(): - return sys.version.split()[0] - -def python_minor_version_string(): - return sys.version[:3] - -def unsupported_python_version(version=sys.version_info): - return version < python_version_unsupported - -def deprecated_python_version(version=sys.version_info): - return version < python_version_deprecated - -if deprecated_python_version(): - msg = r""" -scons: warning: Support for pre-2.7.0 Python version (%s) is deprecated. - If this will cause hardship, contact scons-dev@scons.org -""" - - deprecated_python_expr = re_escape(msg % python_version_string()) + file_expr - del msg -else: - deprecated_python_expr = "" - - -def initialize_sconsflags(ignore_python_version): - """ - Add the --warn=no-python-version option to SCONSFLAGS for every - command so test scripts don't have to filter out Python version - deprecation warnings. - Same for --warn=no-visual-c-missing. - """ - save_sconsflags = os.environ.get('SCONSFLAGS') - if save_sconsflags: - sconsflags = [save_sconsflags] - else: - sconsflags = [] - if ignore_python_version and deprecated_python_version(): - sconsflags.append('--warn=no-python-version') - # Provide a way to suppress or provide alternate flags for - # TestSCons purposes by setting TESTSCONS_SCONSFLAGS. - # (The intended use case is to set it to null when running - # timing tests of earlier versions of SCons which don't - # support the --warn=no-visual-c-missing warning.) - visual_c = os.environ.get('TESTSCONS_SCONSFLAGS', - '--warn=no-visual-c-missing') - if visual_c: - sconsflags.append(visual_c) - os.environ['SCONSFLAGS'] = ' '.join(sconsflags) - return save_sconsflags - -def restore_sconsflags(sconsflags): - if sconsflags is None: - del os.environ['SCONSFLAGS'] - else: - os.environ['SCONSFLAGS'] = sconsflags - - -class TestSCons(TestCommon): - """Class for testing SCons. - - This provides a common place for initializing SCons tests, - eliminating the need to begin every test with the same repeated - initializations. - """ - - scons_version = SConsVersion - javac_is_gcj = False - - def __init__(self, **kw): - """Initialize an SCons testing object. - - If they're not overridden by keyword arguments, this - initializes the object with the following default values: - - program = 'scons' if it exists, - else 'scons.py' - interpreter = 'python' - match = match_exact - workdir = '' - - The workdir value means that, by default, a temporary workspace - directory is created for a TestSCons environment. In addition, - this method changes directory (chdir) to the workspace directory, - so an explicit "chdir = '.'" on all of the run() method calls - is not necessary. - """ - self.orig_cwd = os.getcwd() - self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) - - if not self.external: - try: - script_dir = os.environ['SCONS_SCRIPT_DIR'] - except KeyError: - pass - else: - os.chdir(script_dir) - if 'program' not in kw: - kw['program'] = os.environ.get('SCONS') - if not kw['program']: - if not self.external: - if os.path.exists('scons'): - kw['program'] = 'scons' - else: - kw['program'] = 'scons.py' - else: - kw['program'] = 'scons' - kw['interpreter'] = '' - elif not self.external and not os.path.isabs(kw['program']): - kw['program'] = os.path.join(self.orig_cwd, kw['program']) - if 'interpreter' not in kw and not os.environ.get('SCONS_EXEC'): - kw['interpreter'] = [python, '-tt'] - if 'match' not in kw: - kw['match'] = match_exact - if 'workdir' not in kw: - kw['workdir'] = '' - - # Term causing test failures due to bogus readline init - # control character output on FC8 - # TERM can cause test failures due to control chars in prompts etc. - os.environ['TERM'] = 'dumb' - - self.ignore_python_version = kw.get('ignore_python_version', 1) - if kw.get('ignore_python_version', -1) != -1: - del kw['ignore_python_version'] - - TestCommon.__init__(self, **kw) - - if not self.external: - import SCons.Node.FS - if SCons.Node.FS.default_fs is None: - SCons.Node.FS.default_fs = SCons.Node.FS.FS() - - try: - self.fixture_dirs = (os.environ['FIXTURE_DIRS']).split(os.pathsep) - except KeyError: - pass - - def Environment(self, ENV=None, *args, **kw): - """ - Return a construction Environment that optionally overrides - the default external environment with the specified ENV. - """ - if not self.external: - import SCons.Environment - import SCons.Errors - if not ENV is None: - kw['ENV'] = ENV - try: - return SCons.Environment.Environment(*args, **kw) - except (SCons.Errors.UserError, SCons.Errors.InternalError): - return None - - return None - - def detect(self, var, prog=None, ENV=None, norm=None): - """ - Detect a program named 'prog' by first checking the construction - variable named 'var' and finally searching the path used by - SCons. If either method fails to detect the program, then false - is returned, otherwise the full path to prog is returned. If - prog is None, then the value of the environment variable will be - used as prog. - """ - env = self.Environment(ENV) - if env: - v = env.subst('$'+var) - if not v: - return None - if prog is None: - prog = v - if v != prog: - return None - result = env.WhereIs(prog) - if norm and os.sep != '/': - result = result.replace(os.sep, '/') - return result - - return self.where_is(prog) - - def detect_tool(self, tool, prog=None, ENV=None): - """ - Given a tool (i.e., tool specification that would be passed - to the "tools=" parameter of Environment()) and a program that - corresponds to that tool, return true if and only if we can find - that tool using Environment.Detect(). - - By default, prog is set to the value passed into the tools parameter. - """ - - if not prog: - prog = tool - env = self.Environment(ENV, tools=[tool]) - if env is None: - return None - return env.Detect([prog]) - - def where_is(self, prog, path=None): - """ - Given a program, search for it in the specified external PATH, - or in the actual external PATH if none is specified. - """ - if path is None: - path = os.environ['PATH'] - if self.external: - if isinstance(prog, str): - prog = [prog] - import stat - paths = path.split(os.pathsep) - for p in prog: - for d in paths: - f = os.path.join(d, p) - if os.path.isfile(f): - try: - st = os.stat(f) - except OSError: - # os.stat() raises OSError, not IOError if the file - # doesn't exist, so in this case we let IOError get - # raised so as to not mask possibly serious disk or - # network issues. - continue - if stat.S_IMODE(st[stat.ST_MODE]) & 0o111: - return os.path.normpath(f) - else: - import SCons.Environment - env = SCons.Environment.Environment() - return env.WhereIs(prog, path) - - return None - - def wrap_stdout(self, build_str = "", read_str = "", error = 0, cleaning = 0): - """Wraps standard output string(s) in the normal - "Reading ... done" and "Building ... done" strings - """ - cap,lc = [ ('Build','build'), - ('Clean','clean') ][cleaning] - if error: - term = "scons: %sing terminated because of errors.\n" % lc - else: - term = "scons: done %sing targets.\n" % lc - return "scons: Reading SConscript files ...\n" + \ - read_str + \ - "scons: done reading SConscript files.\n" + \ - "scons: %sing targets ...\n" % cap + \ - build_str + \ - term - - def run(self, *args, **kw): - """ - Set up SCONSFLAGS for every command so test scripts don't need - to worry about unexpected warnings in their output. - """ - sconsflags = initialize_sconsflags(self.ignore_python_version) - try: - TestCommon.run(self, *args, **kw) - finally: - restore_sconsflags(sconsflags) - -# Modifying the options should work and ought to be simpler, but this -# class is used for more than just running 'scons' itself. If there's -# an automated way of determining whether it's running 'scons' or -# something else, this code should be resurected. -# options = kw.get('options') -# if options: -# options = [options] -# else: -# options = [] -# if self.ignore_python_version and deprecated_python_version(): -# options.append('--warn=no-python-version') -# # Provide a way to suppress or provide alternate flags for -# # TestSCons purposes by setting TESTSCONS_SCONSFLAGS. -# # (The intended use case is to set it to null when running -# # timing tests of earlier versions of SCons which don't -# # support the --warn=no-visual-c-missing warning.) -# visual_c = os.environ.get('TESTSCONS_SCONSFLAGS', -# '--warn=no-visual-c-missing') -# if visual_c: -# options.append(visual_c) -# kw['options'] = ' '.join(options) -# TestCommon.run(self, *args, **kw) - - def up_to_date(self, arguments = '.', read_str = "", **kw): - s = "" - for arg in arguments.split(): - s = s + "scons: `%s' is up to date.\n" % arg - kw['arguments'] = arguments - stdout = self.wrap_stdout(read_str = read_str, build_str = s) - # Append '.*' so that timing output that comes after the - # up-to-date output is okay. - kw['stdout'] = re.escape(stdout) + '.*' - kw['match'] = self.match_re_dotall - self.run(**kw) - - def not_up_to_date(self, arguments = '.', **kw): - """Asserts that none of the targets listed in arguments is - up to date, but does not make any assumptions on other targets. - This function is most useful in conjunction with the -n option. - """ - s = "" - for arg in arguments.split(): - s = s + "(?!scons: `%s' is up to date.)" % re.escape(arg) - s = '('+s+'[^\n]*\n)*' - kw['arguments'] = arguments - stdout = re.escape(self.wrap_stdout(build_str='ARGUMENTSGOHERE')) - kw['stdout'] = stdout.replace('ARGUMENTSGOHERE', s) - kw['match'] = self.match_re_dotall - self.run(**kw) - - def option_not_yet_implemented(self, option, arguments=None, **kw): - """ - Verifies expected behavior for options that are not yet implemented: - a warning message, and exit status 1. - """ - msg = "Warning: the %s option is not yet implemented\n" % option - kw['stderr'] = msg - if arguments: - # If it's a long option and the argument string begins with '=', - # it's of the form --foo=bar and needs no separating space. - if option[:2] == '--' and arguments[0] == '=': - kw['arguments'] = option + arguments - else: - kw['arguments'] = option + ' ' + arguments - return self.run(**kw) - - def deprecated_wrap(self, msg): - """ - Calculate the pattern that matches a deprecation warning. - """ - return '\nscons: warning: ' + re_escape(msg) + '\n' + file_expr - - def deprecated_fatal(self, warn, msg): - """ - Determines if the warning has turned into a fatal error. If so, - passes the test, as any remaining runs are now moot. - - This method expects a SConscript to be present that will causes - the warning. The method writes a SConstruct that calls the - SConsscript and looks to see what type of result occurs. - - The pattern that matches the warning is returned. - - TODO: Actually detect that it's now an error. We don't have any - cases yet, so there's no way to test it. - """ - self.write('SConstruct', """if True: - WARN = ARGUMENTS.get('WARN') - if WARN: SetOption('warn', WARN) - SConscript('SConscript') - """) - - def err_out(): - # TODO calculate stderr for fatal error - return re_escape('put something here') - - # no option, should get one of nothing, warning, or error - warning = self.deprecated_wrap(msg) - self.run(arguments = '.', stderr = None) - stderr = self.stderr() - if stderr: - # most common case done first - if match_re_dotall(stderr, warning): - # expected output - pass - elif match_re_dotall(stderr, err_out()): - # now a fatal error; skip the rest of the tests - self.pass_test() - else: - # test failed; have to do this by hand... - print(self.banner('STDOUT ')) - print(self.stdout()) - print(self.diff(warning, stderr, 'STDERR ')) - self.fail_test() - - return warning - - def deprecated_warning(self, warn, msg): - """ - Verifies the expected behavior occurs for deprecation warnings. - This method expects a SConscript to be present that will causes - the warning. The method writes a SConstruct and exercises various - combinations of command-line options and SetOption parameters to - validate that it performs correctly. - - The pattern that matches the warning is returned. - """ - warning = self.deprecated_fatal(warn, msg) - - def RunPair(option, expected): - # run the same test with the option on the command line and - # then with the option passed via SetOption(). - self.run(options = '--warn=' + option, - arguments = '.', - stderr = expected, - match = match_re_dotall) - self.run(options = 'WARN=' + option, - arguments = '.', - stderr = expected, - match = match_re_dotall) - - # all warnings off, should get no output - RunPair('no-deprecated', '') - - # warning enabled, should get expected output - RunPair(warn, warning) - - # warning disabled, should get either nothing or mandatory message - expect = """()|(Can not disable mandataory warning: 'no-%s'\n\n%s)""" % (warn, warning) - RunPair('no-' + warn, expect) - - return warning - - def diff_substr(self, expect, actual, prelen=20, postlen=40): - i = 0 - for x, y in zip(expect, actual): - if x != y: - return "Actual did not match expect at char %d:\n" \ - " Expect: %s\n" \ - " Actual: %s\n" \ - % (i, repr(expect[i-prelen:i+postlen]), - repr(actual[i-prelen:i+postlen])) - i = i + 1 - return "Actual matched the expected output???" - - def python_file_line(self, file, line): - """ - Returns a Python error line for output comparisons. - - The exec of the traceback line gives us the correct format for - this version of Python. - - File "", line 1, - - We stick the requested file name and line number in the right - places, abstracting out the version difference. - """ - # This routine used to use traceback to get the proper format - # that doesn't work well with py3. And the format of the - # traceback seems to be stable, so let's just format - # an appropriate string - # - #exec('import traceback; x = traceback.format_stack()[-1]') - # import traceback - # x = traceback.format_stack() - # x = # XXX: .lstrip() - # x = x.replace('', file) - # x = x.replace('line 1,', 'line %s,' % line) - # x="\n".join(x) - x='File "%s", line %s, in \n'%(file,line) - return x - - def normalize_ps(self, s): - s = re.sub(r'(Creation|Mod)Date: .*', - r'\1Date XXXX', s) - s = re.sub(r'%DVIPSSource:\s+TeX output\s.*', - r'%DVIPSSource: TeX output XXXX', s) - s = re.sub(r'/(BaseFont|FontName) /[A-Z0-9]{6}', - r'/\1 /XXXXXX', s) - s = re.sub(r'BeginFont: [A-Z0-9]{6}', - r'BeginFont: XXXXXX', s) - - return s - - @staticmethod - def to_bytes_re_sub(pattern, repl, str, count=0, flags=0): - """ - Wrapper around re.sub to change pattern and repl to bytes to work with - both python 2 & 3 - """ - pattern = to_bytes(pattern) - repl = to_bytes(repl) - return re.sub(pattern, repl, str, count, flags) - - def normalize_pdf(self, s): - s = self.to_bytes_re_sub(r'/(Creation|Mod)Date \(D:[^)]*\)', - r'/\1Date (D:XXXX)', s) - s = self.to_bytes_re_sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]', - r'/ID [ ]', s) - s = self.to_bytes_re_sub(r'/(BaseFont|FontName) /[A-Z]{6}', - r'/\1 /XXXXXX', s) - s = self.to_bytes_re_sub(r'/Length \d+ *\n/Filter /FlateDecode\n', - r'/Length XXXX\n/Filter /FlateDecode\n', s) - - try: - import zlib - except ImportError: - pass - else: - begin_marker = to_bytes('/FlateDecode\n>>\nstream\n') - end_marker = to_bytes('endstream\nendobj') - - encoded = [] - b = s.find(begin_marker, 0) - while b != -1: - b = b + len(begin_marker) - e = s.find(end_marker, b) - encoded.append((b, e)) - b = s.find(begin_marker, e + len(end_marker)) - - x = 0 - r = [] - for b, e in encoded: - r.append(s[x:b]) - d = zlib.decompress(s[b:e]) - d = self.to_bytes_re_sub(r'%%CreationDate: [^\n]*\n', - r'%%CreationDate: 1970 Jan 01 00:00:00\n', d) - d = self.to_bytes_re_sub(r'%DVIPSSource: TeX output \d\d\d\d\.\d\d\.\d\d:\d\d\d\d', - r'%DVIPSSource: TeX output 1970.01.01:0000', d) - d = self.to_bytes_re_sub(r'/(BaseFont|FontName) /[A-Z]{6}', - r'/\1 /XXXXXX', d) - r.append(d) - x = e - r.append(s[x:]) - s = to_bytes('').join(r) - - return s - - def paths(self,patterns): - import glob - result = [] - for p in patterns: - result.extend(sorted(glob.glob(p))) - return result - - def unlink_sconsignfile(self,name='.sconsign.dblite'): - """ - Delete sconsign file. - Note on python it seems to append .p3 to the file name so we take care of that - Parameters - ---------- - name - expected name of sconsign file - - Returns - ------- - None - """ - if sys.version_info[0] == 3: - name += '.p3' - self.unlink(name) - - def java_ENV(self, version=None): - """ - Initialize with a default external environment that uses a local - Java SDK in preference to whatever's found in the default PATH. - """ - if not self.external: - try: - return self._java_env[version]['ENV'] - except AttributeError: - self._java_env = {} - except KeyError: - pass - - import SCons.Environment - env = SCons.Environment.Environment() - self._java_env[version] = env - - - if version: - patterns = [ - '/usr/java/jdk%s*/bin' % version, - '/usr/lib/jvm/*-%s*/bin' % version, - '/usr/local/j2sdk%s*/bin' % version, - ] - java_path = self.paths(patterns) + [env['ENV']['PATH']] - else: - patterns = [ - '/usr/java/latest/bin', - '/usr/lib/jvm/*/bin', - '/usr/local/j2sdk*/bin', - ] - java_path = self.paths(patterns) + [env['ENV']['PATH']] - - env['ENV']['PATH'] = os.pathsep.join(java_path) - return env['ENV'] - - return None - - def java_where_includes(self,version=None): - """ - Return java include paths compiling java jni code - """ - import sys - - result = [] - if sys.platform[:6] == 'darwin': - java_home = self.java_where_java_home(version) - jni_path = os.path.join(java_home,'include','jni.h') - if os.path.exists(jni_path): - result.append(os.path.dirname(jni_path)) - - if not version: - version='' - jni_dirs = ['/System/Library/Frameworks/JavaVM.framework/Headers/jni.h', - '/usr/lib/jvm/default-java/include/jni.h', - '/usr/lib/jvm/java-*-oracle/include/jni.h'] - else: - jni_dirs = ['/System/Library/Frameworks/JavaVM.framework/Versions/%s*/Headers/jni.h'%version] - jni_dirs.extend(['/usr/lib/jvm/java-*-sun-%s*/include/jni.h'%version, - '/usr/lib/jvm/java-%s*-openjdk*/include/jni.h'%version, - '/usr/java/jdk%s*/include/jni.h'%version]) - dirs = self.paths(jni_dirs) - if not dirs: - return None - d=os.path.dirname(self.paths(jni_dirs)[0]) - result.append(d) - - if sys.platform == 'win32': - result.append(os.path.join(d,'win32')) - elif sys.platform.startswith('linux'): - result.append(os.path.join(d,'linux')) - return result - - def java_where_java_home(self, version=None): - if sys.platform[:6] == 'darwin': - # osx 10.11, 10.12 - home_tool = '/usr/libexec/java_home' - java_home = False - if os.path.exists(home_tool): - java_home = subprocess.check_output(home_tool).strip() - java_home = java_home.decode() - - if version is None: - if java_home: - return java_home - else: - homes = ['/System/Library/Frameworks/JavaVM.framework/Home', - # osx 10.10 - '/System/Library/Frameworks/JavaVM.framework/Versions/Current/Home'] - for home in homes: - if os.path.exists(home): - return home - - else: - if java_home.find('jdk%s'%version) != -1: - return java_home - else: - home = '/System/Library/Frameworks/JavaVM.framework/Versions/%s/Home' % version - if not os.path.exists(home): - # This works on OSX 10.10 - home = '/System/Library/Frameworks/JavaVM.framework/Versions/Current/' - else: - jar = self.java_where_jar(version) - home = os.path.normpath('%s/..'%jar) - if os.path.isdir(home): - return home - print("Could not determine JAVA_HOME: %s is not a directory" % home) - self.fail_test() - - def java_mac_check(self, where_java_bin, java_bin_name): - # on Mac there is a place holder java installed to start the java install process - # so we need to check the output in this case, more info here: - # http://anas.pk/2015/09/02/solution-no-java-runtime-present-mac-yosemite/ - sp = subprocess.Popen([where_java_bin, "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = sp.communicate() - sp.wait() - if("No Java runtime" in str(stderr)): - self.skip_test("Could not find Java " + java_bin_name + ", skipping test(s).\n") - - def java_where_jar(self, version=None): - ENV = self.java_ENV(version) - if self.detect_tool('jar', ENV=ENV): - where_jar = self.detect('JAR', 'jar', ENV=ENV) - else: - where_jar = self.where_is('jar', ENV['PATH']) - if not where_jar: - self.skip_test("Could not find Java jar, skipping test(s).\n") - elif sys.platform == "darwin": - self.java_mac_check(where_jar, 'jar') - - return where_jar - - def java_where_java(self, version=None): - """ - Return a path to the java executable. - """ - ENV = self.java_ENV(version) - where_java = self.where_is('java', ENV['PATH']) - - if not where_java: - self.skip_test("Could not find Java java, skipping test(s).\n") - elif sys.platform == "darwin": - self.java_mac_check(where_java, 'java') - - return where_java - - def java_where_javac(self, version=None): - """ - Return a path to the javac compiler. - """ - ENV = self.java_ENV(version) - if self.detect_tool('javac'): - where_javac = self.detect('JAVAC', 'javac', ENV=ENV) - else: - where_javac = self.where_is('javac', ENV['PATH']) - if not where_javac: - self.skip_test("Could not find Java javac, skipping test(s).\n") - elif sys.platform == "darwin": - self.java_mac_check(where_javac, 'javac') - - self.run(program = where_javac, - arguments = '-version', - stderr=None, - status=None) - if version: - if self.stderr().find('javac %s' % version) == -1: - fmt = "Could not find javac for Java version %s, skipping test(s).\n" - self.skip_test(fmt % version) - else: - m = re.search(r'javac (\d\.\d)', self.stderr()) - if m: - version = m.group(1) - self.javac_is_gcj = False - elif self.stderr().find('gcj'): - version='1.2' - self.javac_is_gcj = True - else: - version = None - self.javac_is_gcj = False - return where_javac, version - - def java_where_javah(self, version=None): - ENV = self.java_ENV(version) - if self.detect_tool('javah'): - where_javah = self.detect('JAVAH', 'javah', ENV=ENV) - else: - where_javah = self.where_is('javah', ENV['PATH']) - if not where_javah: - self.skip_test("Could not find Java javah, skipping test(s).\n") - return where_javah - - def java_where_rmic(self, version=None): - ENV = self.java_ENV(version) - if self.detect_tool('rmic'): - where_rmic = self.detect('RMIC', 'rmic', ENV=ENV) - else: - where_rmic = self.where_is('rmic', ENV['PATH']) - if not where_rmic: - self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n") - return where_rmic - - - def java_get_class_files(self, dir): - result = [] - for dirpath, dirnames, filenames in os.walk(dir): - for fname in filenames: - if fname.endswith('.class'): - result.append(os.path.join(dirpath, fname)) - return sorted(result) - - - def Qt_dummy_installation(self, dir='qt'): - # create a dummy qt installation - - self.subdir( dir, [dir, 'bin'], [dir, 'include'], [dir, 'lib'] ) - - self.write([dir, 'bin', 'mymoc.py'], """\ -import getopt -import sys -import re -# -w and -z are fake options used in test/QT/QTFLAGS.py -cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:wz', []) -output = None -impl = 0 -opt_string = '' -for opt, arg in cmd_opts: - if opt == '-o': output = open(arg, 'w') - elif opt == '-i': impl = 1 - else: opt_string = opt_string + ' ' + opt -output.write("/* mymoc.py%s */\\n" % opt_string) -for a in args: - contents = open(a, 'r').read() - a = a.replace('\\\\', '\\\\\\\\') - subst = r'{ my_qt_symbol( "' + a + '\\\\n" ); }' - if impl: - contents = re.sub( r'#include.*', '', contents ) - output.write(contents.replace('Q_OBJECT', subst)) -output.close() -sys.exit(0) -""") - - self.write([dir, 'bin', 'myuic.py'], """\ -import os.path -import re -import sys -output_arg = 0 -impl_arg = 0 -impl = None -source = None -opt_string = '' -for arg in sys.argv[1:]: - if output_arg: - output = open(arg, 'w') - output_arg = 0 - elif impl_arg: - impl = arg - impl_arg = 0 - elif arg == "-o": - output_arg = 1 - elif arg == "-impl": - impl_arg = 1 - elif arg[0:1] == "-": - opt_string = opt_string + ' ' + arg - else: - if source: - sys.exit(1) - source = open(arg, 'r') - sourceFile = arg -output.write("/* myuic.py%s */\\n" % opt_string) -if impl: - output.write( '#include "' + impl + '"\\n' ) - includes = re.findall('(.*?)', source.read()) - for incFile in includes: - # this is valid for ui.h files, at least - if os.path.exists(incFile): - output.write('#include "' + incFile + '"\\n') -else: - output.write( '#include "my_qobject.h"\\n' + source.read() + " Q_OBJECT \\n" ) -output.close() -sys.exit(0) -""" ) - - self.write([dir, 'include', 'my_qobject.h'], r""" -#define Q_OBJECT ; -void my_qt_symbol(const char *arg); -""") - - self.write([dir, 'lib', 'my_qobject.cpp'], r""" -#include "../include/my_qobject.h" -#include -void my_qt_symbol(const char *arg) { - fputs( arg, stdout ); -} -""") - - self.write([dir, 'lib', 'SConstruct'], r""" -env = Environment() -import sys -if sys.platform == 'win32': - env.StaticLibrary( 'myqt', 'my_qobject.cpp' ) -else: - env.SharedLibrary( 'myqt', 'my_qobject.cpp' ) -""") - - self.run(chdir = self.workpath(dir, 'lib'), - arguments = '.', - stderr = noisy_ar, - match = self.match_re_dotall) - - self.QT = self.workpath(dir) - self.QT_LIB = 'myqt' - self.QT_MOC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'mymoc.py')) - self.QT_UIC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'myuic.py')) - self.QT_LIB_DIR = self.workpath(dir, 'lib') - - def Qt_create_SConstruct(self, place): - if isinstance(place, list): - place = test.workpath(*place) - self.write(place, """\ -if ARGUMENTS.get('noqtdir', 0): QTDIR=None -else: QTDIR=r'%s' -env = Environment(QTDIR = QTDIR, - QT_LIB = r'%s', - QT_MOC = r'%s', - QT_UIC = r'%s', - tools=['default','qt']) -dup = 1 -if ARGUMENTS.get('variant_dir', 0): - if ARGUMENTS.get('chdir', 0): - SConscriptChdir(1) - else: - SConscriptChdir(0) - dup=int(ARGUMENTS.get('dup', 1)) - if dup == 0: - builddir = 'build_dup0' - env['QT_DEBUG'] = 1 - else: - builddir = 'build' - VariantDir(builddir, '.', duplicate=dup) - print(builddir, dup) - sconscript = Dir(builddir).File('SConscript') -else: - sconscript = File('SConscript') -Export("env dup") -SConscript( sconscript ) -""" % (self.QT, self.QT_LIB, self.QT_MOC, self.QT_UIC)) - - - NCR = 0 # non-cached rebuild - CR = 1 # cached rebuild (up to date) - NCF = 2 # non-cached build failure - CF = 3 # cached build failure - - if sys.platform == 'win32': - Configure_lib = 'msvcrt' - else: - Configure_lib = 'm' - - # to use cygwin compilers on cmd.exe -> uncomment following line - #Configure_lib = 'm' - - def coverage_run(self): - """ Check if the the tests are being run under coverage. - """ - return 'COVERAGE_PROCESS_START' in os.environ or 'COVERAGE_FILE' in os.environ - - def skip_if_not_msvc(self, check_platform=True): - """ Check whether we are on a Windows platform and skip the - test if not. This check can be omitted by setting - check_platform to False. - Then, for a win32 platform, additionally check - whether we have a MSVC toolchain installed - in the system, and skip the test if none can be - found (=MinGW is the only compiler available). - """ - if check_platform: - if sys.platform != 'win32': - msg = "Skipping Visual C/C++ test on non-Windows platform '%s'\n" % sys.platform - self.skip_test(msg) - return - - try: - import SCons.Tool.MSCommon as msc - if not msc.msvc_exists(): - msg = "No MSVC toolchain found...skipping test\n" - self.skip_test(msg) - except: - pass - - def checkLogAndStdout(self, checks, results, cached, - logfile, sconf_dir, sconstruct, - doCheckLog=True, doCheckStdout=True): - """ - Used to verify the expected output from using Configure() - via the contents of one or both of stdout or config.log file. - The checks, results, cached parameters all are zipped together - for use in comparing results. - - TODO: Perhaps a better API makes sense? - - Parameters - ---------- - checks : The Configure checks being run - - results : The expected results for each check - - cached : If the corresponding check is expected to be cached - - logfile : Name of the config log - - sconf_dir : Name of the sconf dir - - sconstruct : SConstruct file name - - doCheckLog : check specified log file, defaults to true - - doCheckStdout : Check stdout, defaults to true - - Returns - ------- - - """ - - class NoMatch(Exception): - def __init__(self, p): - self.pos = p - - def matchPart(log, logfile, lastEnd, NoMatch=NoMatch): - """ - Match part of the logfile - """ - m = re.match(log, logfile[lastEnd:]) - if not m: - raise NoMatch(lastEnd) - return m.end() + lastEnd - - try: - - # Build regexp for a character which is not - # a linesep, and in the case of CR/LF - # build it with both CR and CR/LF - # TODO: Not sure why this is a good idea. A static string - # could do the same since we only have two variations - # to do with? - # ls = os.linesep - # nols = "(" - # for i in range(len(ls)): - # nols = nols + "(" - # for j in range(i): - # nols = nols + ls[j] - # nols = nols + "[^" + ls[i] + "])" - # if i < len(ls)-1: - # nols = nols + "|" - # nols = nols + ")" - # - # Replaced above logic with \n as we're reading the file - # using non-binary read. Python will translate \r\n -> \n - # For us. - ls = '\n' - nols = '([^\n])' - lastEnd = 0 - - # Read the whole logfile - logfile = self.read(self.workpath(logfile), mode='r') - - # Some debug code to keep around.. - # sys.stderr.write("LOGFILE[%s]:%s"%(type(logfile),logfile)) - - if (doCheckLog and - logfile.find("scons: warning: The stored build information has an unexpected class.") >= 0): - self.fail_test() - - sconf_dir = sconf_dir - sconstruct = sconstruct - - log = r'file\ \S*%s\,line \d+:' % re.escape(sconstruct) + ls - if doCheckLog: - lastEnd = matchPart(log, logfile, lastEnd) - - log = "\t" + re.escape("Configure(confdir = %s)" % sconf_dir) + ls - if doCheckLog: - lastEnd = matchPart(log, logfile, lastEnd) - - rdstr = "" - cnt = 0 - for check,result,cache_desc in zip(checks, results, cached): - log = re.escape("scons: Configure: " + check) + ls - - if doCheckLog: - lastEnd = matchPart(log, logfile, lastEnd) - - log = "" - result_cached = 1 - for bld_desc in cache_desc: # each TryXXX - for ext, flag in bld_desc: # each file in TryBuild - file = os.path.join(sconf_dir,"conftest_%d%s" % (cnt, ext)) - if flag == self.NCR: - # NCR = Non Cached Rebuild - # rebuild will pass - if ext in ['.c', '.cpp']: - log=log + re.escape(file + " <-") + ls - log=log + r"( \|" + nols + "*" + ls + ")+?" - else: - log=log + "(" + nols + "*" + ls +")*?" - result_cached = 0 - if flag == self.CR: - # CR = cached rebuild (up to date)s - # up to date - log=log + \ - re.escape("scons: Configure: \"%s\" is up to date." - % file) + ls - log=log+re.escape("scons: Configure: The original builder " - "output was:") + ls - log=log+r"( \|.*"+ls+")+" - if flag == self.NCF: - # non-cached rebuild failure - log=log + "(" + nols + "*" + ls + ")*?" - result_cached = 0 - if flag == self.CF: - # cached rebuild failure - log=log + \ - re.escape("scons: Configure: Building \"%s\" failed " - "in a previous run and all its sources are" - " up to date." % file) + ls - log=log+re.escape("scons: Configure: The original builder " - "output was:") + ls - log=log+r"( \|.*"+ls+")+" - cnt = cnt + 1 - if result_cached: - result = "(cached) " + result - rdstr = rdstr + re.escape(check) + re.escape(result) + "\n" - log=log + re.escape("scons: Configure: " + result) + ls + ls - - if doCheckLog: - lastEnd = matchPart(log, logfile, lastEnd) - - log = "" - if doCheckLog: lastEnd = matchPart(ls, logfile, lastEnd) - if doCheckLog and lastEnd != len(logfile): - raise NoMatch(lastEnd) - - except NoMatch as m: - print("Cannot match log file against log regexp.") - print("log file: ") - print("------------------------------------------------------") - print(logfile[m.pos:]) - print("------------------------------------------------------") - print("log regexp: ") - print("------------------------------------------------------") - print(log) - print("------------------------------------------------------") - self.fail_test() - - if doCheckStdout: - exp_stdout = self.wrap_stdout(".*", rdstr) - if not self.match_re_dotall(self.stdout(), exp_stdout): - print("Unexpected stdout: ") - print("-----------------------------------------------------") - print(repr(self.stdout())) - print("-----------------------------------------------------") - print(repr(exp_stdout)) - print("-----------------------------------------------------") - self.fail_test() - - def get_python_version(self): - """ - Returns the Python version (just so everyone doesn't have to - hand-code slicing the right number of characters). - """ - # see also sys.prefix documentation - return python_minor_version_string() - - def get_platform_python_info(self): - """ - Returns a path to a Python executable suitable for testing on - this platform and its associated include path, library path, - and library name. - """ - python = self.where_is('python') - if not python: - self.skip_test('Can not find installed "python", skipping test.\n') - - self.run(program = python, stdin = """\ -import os, sys -try: - if sys.platform == 'win32': - py_ver = 'python%d%d' % sys.version_info[:2] - else: - py_ver = 'python%d.%d' % sys.version_info[:2] -except AttributeError: - py_ver = 'python' + sys.version[:3] -# print include and lib path -try: - import distutils.sysconfig - exec_prefix = distutils.sysconfig.EXEC_PREFIX - print(distutils.sysconfig.get_python_inc()) - lib_path = os.path.join(exec_prefix, 'libs') - if not os.path.exists(lib_path): - lib_path = os.path.join(exec_prefix, 'lib') - print(lib_path) -except: - print(os.path.join(sys.prefix, 'include', py_ver)) - print(os.path.join(sys.prefix, 'lib', py_ver, 'config')) -print(py_ver) -""") - - return [python] + self.stdout().strip().split('\n') - - def start(self, *args, **kw): - """ - Starts SCons in the test environment. - - This method exists to tell Test{Cmd,Common} that we're going to - use standard input without forcing every .start() call in the - individual tests to do so explicitly. - """ - if 'stdin' not in kw: - kw['stdin'] = True - sconsflags = initialize_sconsflags(self.ignore_python_version) - try: - p = TestCommon.start(self, *args, **kw) - finally: - restore_sconsflags(sconsflags) - return p - - def wait_for(self, fname, timeout=20.0, popen=None): - """ - Waits for the specified file name to exist. - """ - waited = 0.0 - while not os.path.exists(fname): - if timeout and waited >= timeout: - sys.stderr.write('timed out waiting for %s to exist\n' % fname) - if popen: - popen.stdin.close() - popen.stdin = None - self.status = 1 - self.finish(popen) - stdout = self.stdout() - if stdout: - sys.stdout.write(self.banner('STDOUT ') + '\n') - sys.stdout.write(stdout) - stderr = self.stderr() - if stderr: - sys.stderr.write(self.banner('STDERR ') + '\n') - sys.stderr.write(stderr) - self.fail_test() - time.sleep(1.0) - waited = waited + 1.0 - - def get_alt_cpp_suffix(self): - """ - Many CXX tests have this same logic. - They all needed to determine if the current os supports - files with .C and .c as different files or not - in which case they are instructed to use .cpp instead of .C - """ - if not case_sensitive_suffixes('.c','.C'): - alt_cpp_suffix = '.cpp' - else: - alt_cpp_suffix = '.C' - return alt_cpp_suffix - - def platform_has_symlink(self): - if not hasattr(os, 'symlink') or sys.platform == 'win32': - return False - else: - return True - - -class Stat: - def __init__(self, name, units, expression, convert=None): - if convert is None: - convert = lambda x: x - self.name = name - self.units = units - self.expression = re.compile(expression) - self.convert = convert - -StatList = [ - Stat('memory-initial', 'kbytes', - r'Memory before reading SConscript files:\s+(\d+)', - convert=lambda s: int(s) // 1024), - Stat('memory-prebuild', 'kbytes', - r'Memory before building targets:\s+(\d+)', - convert=lambda s: int(s) // 1024), - Stat('memory-final', 'kbytes', - r'Memory after building targets:\s+(\d+)', - convert=lambda s: int(s) // 1024), - - Stat('time-sconscript', 'seconds', - r'Total SConscript file execution time:\s+([\d.]+) seconds'), - Stat('time-scons', 'seconds', - r'Total SCons execution time:\s+([\d.]+) seconds'), - Stat('time-commands', 'seconds', - r'Total command execution time:\s+([\d.]+) seconds'), - Stat('time-total', 'seconds', - r'Total build time:\s+([\d.]+) seconds'), -] - - -class TimeSCons(TestSCons): - """Class for timing SCons.""" - def __init__(self, *args, **kw): - """ - In addition to normal TestSCons.TestSCons intialization, - this enables verbose mode (which causes the command lines to - be displayed in the output) and copies the contents of the - directory containing the executing script to the temporary - working directory. - """ - self.variables = kw.get('variables') - default_calibrate_variables = [] - if self.variables is not None: - for variable, value in self.variables.items(): - value = os.environ.get(variable, value) - try: - value = int(value) - except ValueError: - try: - value = float(value) - except ValueError: - pass - else: - default_calibrate_variables.append(variable) - else: - default_calibrate_variables.append(variable) - self.variables[variable] = value - del kw['variables'] - calibrate_keyword_arg = kw.get('calibrate') - if calibrate_keyword_arg is None: - self.calibrate_variables = default_calibrate_variables - else: - self.calibrate_variables = calibrate_keyword_arg - del kw['calibrate'] - - self.calibrate = os.environ.get('TIMESCONS_CALIBRATE', '0') != '0' - - if 'verbose' not in kw and not self.calibrate: - kw['verbose'] = True - - TestSCons.__init__(self, *args, **kw) - - # TODO(sgk): better way to get the script dir than sys.argv[0] - self.test_dir = os.path.dirname(sys.argv[0]) - test_name = os.path.basename(self.test_dir) - - if not os.path.isabs(self.test_dir): - self.test_dir = os.path.join(self.orig_cwd, self.test_dir) - self.copy_timing_configuration(self.test_dir, self.workpath()) - - def main(self, *args, **kw): - """ - The main entry point for standard execution of timings. - - This method run SCons three times: - - Once with the --help option, to have it exit after just reading - the configuration. - - Once as a full build of all targets. - - Once again as a (presumably) null or up-to-date build of - all targets. - - The elapsed time to execute each build is printed after - it has finished. - """ - if 'options' not in kw and self.variables: - options = [] - for variable, value in self.variables.items(): - options.append('%s=%s' % (variable, value)) - kw['options'] = ' '.join(options) - if self.calibrate: - self.calibration(*args, **kw) - else: - self.uptime() - self.startup(*args, **kw) - self.full(*args, **kw) - self.null(*args, **kw) - - def trace(self, graph, name, value, units, sort=None): - fmt = "TRACE: graph=%s name=%s value=%s units=%s" - line = fmt % (graph, name, value, units) - if sort is not None: - line = line + (' sort=%s' % sort) - line = line + '\n' - sys.stdout.write(line) - sys.stdout.flush() - - def report_traces(self, trace, stats): - self.trace('TimeSCons-elapsed', - trace, - self.elapsed_time(), - "seconds", - sort=0) - for name, args in stats.items(): - self.trace(name, trace, **args) - - def uptime(self): - try: - fp = open('/proc/loadavg') - except EnvironmentError: - pass - else: - avg1, avg5, avg15 = fp.readline().split(" ")[:3] - fp.close() - self.trace('load-average', 'average1', avg1, 'processes') - self.trace('load-average', 'average5', avg5, 'processes') - self.trace('load-average', 'average15', avg15, 'processes') - - def collect_stats(self, input): - result = {} - for stat in StatList: - m = stat.expression.search(input) - if m: - value = stat.convert(m.group(1)) - # The dict keys match the keyword= arguments - # of the trace() method above so they can be - # applied directly to that call. - result[stat.name] = {'value':value, 'units':stat.units} - return result - - def add_timing_options(self, kw, additional=None): - """ - Add the necessary timings options to the kw['options'] value. - """ - options = kw.get('options', '') - if additional is not None: - options += additional - kw['options'] = options + ' --debug=memory,time' - - def startup(self, *args, **kw): - """ - Runs scons with the --help option. - - This serves as a way to isolate just the amount of startup time - spent reading up the configuration, since --help exits before any - "real work" is done. - """ - self.add_timing_options(kw, ' --help') - # Ignore the exit status. If the --help run dies, we just - # won't report any statistics for it, but we can still execute - # the full and null builds. - kw['status'] = None - self.run(*args, **kw) - sys.stdout.write(self.stdout()) - stats = self.collect_stats(self.stdout()) - # Delete the time-commands, since no commands are ever - # executed on the help run and it is (or should be) always 0.0. - del stats['time-commands'] - self.report_traces('startup', stats) - - def full(self, *args, **kw): - """ - Runs a full build of SCons. - """ - self.add_timing_options(kw) - self.run(*args, **kw) - sys.stdout.write(self.stdout()) - stats = self.collect_stats(self.stdout()) - self.report_traces('full', stats) - self.trace('full-memory', 'initial', **stats['memory-initial']) - self.trace('full-memory', 'prebuild', **stats['memory-prebuild']) - self.trace('full-memory', 'final', **stats['memory-final']) - - def calibration(self, *args, **kw): - """ - Runs a full build of SCons, but only reports calibration - information (the variable(s) that were set for this configuration, - and the elapsed time to run. - """ - self.add_timing_options(kw) - self.run(*args, **kw) - for variable in self.calibrate_variables: - value = self.variables[variable] - sys.stdout.write('VARIABLE: %s=%s\n' % (variable, value)) - sys.stdout.write('ELAPSED: %s\n' % self.elapsed_time()) - - def null(self, *args, **kw): - """ - Runs an up-to-date null build of SCons. - """ - # TODO(sgk): allow the caller to specify the target (argument) - # that must be up-to-date. - self.add_timing_options(kw) - self.up_to_date(arguments='.', **kw) - sys.stdout.write(self.stdout()) - stats = self.collect_stats(self.stdout()) - # time-commands should always be 0.0 on a null build, because - # no commands should be executed. Remove it from the stats - # so we don't trace it, but only if it *is* 0 so that we'll - # get some indication if a supposedly-null build actually does - # build something. - if float(stats['time-commands']['value']) == 0.0: - del stats['time-commands'] - self.report_traces('null', stats) - self.trace('null-memory', 'initial', **stats['memory-initial']) - self.trace('null-memory', 'prebuild', **stats['memory-prebuild']) - self.trace('null-memory', 'final', **stats['memory-final']) - - def elapsed_time(self): - """ - Returns the elapsed time of the most recent command execution. - """ - return self.endTime - self.startTime - - def run(self, *args, **kw): - """ - Runs a single build command, capturing output in the specified file. - - Because this class is about timing SCons, we record the start - and end times of the elapsed execution, and also add the - --debug=memory and --debug=time options to have SCons report - its own memory and timing statistics. - """ - self.startTime = time.time() - try: - result = TestSCons.run(self, *args, **kw) - finally: - self.endTime = time.time() - return result - - def copy_timing_configuration(self, source_dir, dest_dir): - """ - Copies the timing configuration from the specified source_dir (the - directory in which the controlling script lives) to the specified - dest_dir (a temporary working directory). - - This ignores all files and directories that begin with the string - 'TimeSCons-', and all '.svn' subdirectories. - """ - for root, dirs, files in os.walk(source_dir): - if '.svn' in dirs: - dirs.remove('.svn') - dirs = [ d for d in dirs if not d.startswith('TimeSCons-') ] - files = [ f for f in files if not f.startswith('TimeSCons-') ] - for dirname in dirs: - source = os.path.join(root, dirname) - destination = source.replace(source_dir, dest_dir) - os.mkdir(destination) - if sys.platform != 'win32': - shutil.copystat(source, destination) - for filename in files: - source = os.path.join(root, filename) - destination = source.replace(source_dir, dest_dir) - shutil.copy2(source, destination) - - -# In some environments, $AR will generate a warning message to stderr -# if the library doesn't previously exist and is being created. One -# way to fix this is to tell AR to be quiet (sometimes the 'c' flag), -# but this is difficult to do in a platform-/implementation-specific -# method. Instead, we will use the following as a stderr match for -# tests that use AR so that we will view zero or more "ar: creating -# " messages to be successful executions of the test (see -# test/AR.py for sample usage). - -noisy_ar=r'(ar: creating( archive)? \S+\n?)*' - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/QMTest/TestSConsMSVS.py b/QMTest/TestSConsMSVS.py deleted file mode 100644 index 1e879d9..0000000 --- a/QMTest/TestSConsMSVS.py +++ /dev/null @@ -1,1177 +0,0 @@ -""" -TestSConsMSVS.py: a testing framework for the SCons software construction -tool. - -A TestSConsMSVS environment object is created via the usual invocation: - - test = TestSConsMSVS() - -TestSConsMSVS is a subsclass of TestSCons, which is in turn a subclass -of TestCommon, which is in turn is a subclass of TestCmd), and hence -has available all of the methods and attributes from those classes, -as well as any overridden or additional methods or attributes defined -in this subclass. -""" - -# __COPYRIGHT__ - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - -import os -import sys -import platform -import traceback -from xml.etree import ElementTree - -from TestSCons import * -from TestSCons import __all__ - - - -expected_dspfile_6_0 = '''\ -# Microsoft Developer Studio Project File - Name="Test" - Package Owner=<4> -# Microsoft Developer Studio Generated Build File, Format Version 6.00 -# ** DO NOT EDIT ** - -# TARGTYPE "Win32 (x86) External Target" 0x0106 - -CFG=Test - Win32 Release -!MESSAGE This is not a valid makefile. To build this project using NMAKE, -!MESSAGE use the Export Makefile command and run -!MESSAGE -!MESSAGE NMAKE /f "Test.mak". -!MESSAGE -!MESSAGE You can specify a configuration when running NMAKE -!MESSAGE by defining the macro CFG on the command line. For example: -!MESSAGE -!MESSAGE NMAKE /f "Test.mak" CFG="Test - Win32 Release" -!MESSAGE -!MESSAGE Possible choices for configuration are: -!MESSAGE -!MESSAGE "Test - Win32 Release" (based on "Win32 (x86) External Target") -!MESSAGE - -# Begin Project -# PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "" -# PROP Scc_LocalPath "" - -!IF "$(CFG)" == "Test - Win32 Release" - -# PROP BASE Use_MFC 0 -# PROP BASE Use_Debug_Libraries 0 -# PROP BASE Output_Dir "" -# PROP BASE Intermediate_Dir "" -# PROP BASE Cmd_Line "echo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe"" -# PROP BASE Rebuild_Opt "-c && echo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe"" -# PROP BASE Target_File "Test.exe" -# PROP BASE Bsc_Name "" -# PROP BASE Target_Dir "" -# PROP Use_MFC 0 -# PROP Use_Debug_Libraries 0 -# PROP Output_Dir "" -# PROP Intermediate_Dir "" -# PROP Cmd_Line "echo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe"" -# PROP Rebuild_Opt "-c && echo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe"" -# PROP Target_File "Test.exe" -# PROP Bsc_Name "" -# PROP Target_Dir "" - -!ENDIF - -# Begin Target - -# Name "Test - Win32 Release" - -!IF "$(CFG)" == "Test - Win32 Release" - -!ENDIF - -# Begin Group "Header Files" - -# PROP Default_Filter "h;hpp;hxx;hm;inl" -# Begin Source File - -SOURCE="sdk.h" -# End Source File -# End Group -# Begin Group "Local Headers" - -# PROP Default_Filter "h;hpp;hxx;hm;inl" -# Begin Source File - -SOURCE="test.h" -# End Source File -# End Group -# Begin Group "Other Files" - -# PROP Default_Filter "" -# Begin Source File - -SOURCE="readme.txt" -# End Source File -# End Group -# Begin Group "Resource Files" - -# PROP Default_Filter "r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" -# Begin Source File - -SOURCE="test.rc" -# End Source File -# End Group -# Begin Group "Source Files" - -# PROP Default_Filter "cpp;c;cxx;l;y;def;odl;idl;hpj;bat" -# Begin Source File - -SOURCE="test.c" -# End Source File -# End Group -# Begin Source File - -SOURCE="" -# End Source File -# End Target -# End Project -''' - -expected_dswfile_6_0 = '''\ -Microsoft Developer Studio Workspace File, Format Version 6.00 -# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! - -############################################################################### - -Project: "Test"="Test.dsp" - Package Owner=<4> - -Package=<5> -{{{ -}}} - -Package=<4> -{{{ -}}} - -############################################################################### - -Global: - -Package=<5> -{{{ -}}} - -Package=<3> -{{{ -}}} - -############################################################################### -''' - -SConscript_contents_6_0 = """\ -env=Environment(platform='win32', tools=['msvs'], - MSVS_VERSION='6.0',HOST_ARCH='%(HOST_ARCH)s') - -testsrc = ['test.c'] -testincs = ['sdk.h'] -testlocalincs = ['test.h'] -testresources = ['test.rc'] -testmisc = ['readme.txt'] - -env.MSVSProject(target = 'Test.dsp', - srcs = testsrc, - incs = testincs, - localincs = testlocalincs, - resources = testresources, - misc = testmisc, - buildtarget = 'Test.exe', - variant = 'Release') -""" - - - -expected_slnfile_7_0 = """\ -Microsoft Visual Studio Solution File, Format Version 7.00 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "" -EndProject -Global - -\tGlobalSection(SolutionConfiguration) = preSolution -\t\tConfigName.0 = Release -\tEndGlobalSection -\tGlobalSection(ProjectDependencies) = postSolution -\tEndGlobalSection -\tGlobalSection(ProjectConfiguration) = postSolution -\t\t.Release.ActiveCfg = Release|Win32 -\t\t.Release.Build.0 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(ExtensibilityGlobals) = postSolution -\tEndGlobalSection -\tGlobalSection(ExtensibilityAddIns) = postSolution -\tEndGlobalSection -EndGlobal -""" - -expected_vcprojfile_7_0 = """\ - - -\tKeyword="MakeFileProj"> -\t -\t\t -\t -\t -\t\t -\t\t\t -\t\t -\t -\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t -\t -\t -\t - -""" - -SConscript_contents_7_0 = """\ -env=Environment(platform='win32', tools=['msvs'], - MSVS_VERSION='7.0',HOST_ARCH='%(HOST_ARCH)s') - -testsrc = ['test1.cpp', 'test2.cpp'] -testincs = ['sdk.h'] -testlocalincs = ['test.h'] -testresources = ['test.rc'] -testmisc = ['readme.txt'] - -env.MSVSProject(target = 'Test.vcproj', - slnguid = '{SLNGUID}', - srcs = testsrc, - incs = testincs, - localincs = testlocalincs, - resources = testresources, - misc = testmisc, - buildtarget = 'Test.exe', - variant = 'Release') -""" - - - -expected_slnfile_7_1 = """\ -Microsoft Visual Studio Solution File, Format Version 8.00 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "" -\tProjectSection(ProjectDependencies) = postProject -\tEndProjectSection -EndProject -Global - -\tGlobalSection(SolutionConfiguration) = preSolution -\t\tConfigName.0 = Release -\tEndGlobalSection -\tGlobalSection(ProjectDependencies) = postSolution -\tEndGlobalSection -\tGlobalSection(ProjectConfiguration) = postSolution -\t\t.Release.ActiveCfg = Release|Win32 -\t\t.Release.Build.0 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(ExtensibilityGlobals) = postSolution -\tEndGlobalSection -\tGlobalSection(ExtensibilityAddIns) = postSolution -\tEndGlobalSection -EndGlobal -""" - -expected_vcprojfile_7_1 = """\ - - -\tKeyword="MakeFileProj"> -\t -\t\t -\t -\t -\t\t -\t\t\t -\t\t -\t -\t -\t -\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t -\t -\t -\t - -""" - -SConscript_contents_7_1 = """\ -env=Environment(platform='win32', tools=['msvs'], - MSVS_VERSION='7.1',HOST_ARCH='%(HOST_ARCH)s') - -testsrc = ['test1.cpp', 'test2.cpp'] -testincs = ['sdk.h'] -testlocalincs = ['test.h'] -testresources = ['test.rc'] -testmisc = ['readme.txt'] - -env.MSVSProject(target = 'Test.vcproj', - slnguid = '{SLNGUID}', - srcs = testsrc, - incs = testincs, - localincs = testlocalincs, - resources = testresources, - misc = testmisc, - buildtarget = 'Test.exe', - variant = 'Release') -""" - - - -expected_slnfile_8_0 = """\ -Microsoft Visual Studio Solution File, Format Version 9.00 -# Visual Studio 2005 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "" -EndProject -Global - -\tGlobalSection(SolutionConfigurationPlatforms) = preSolution -\t\tRelease|Win32 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(ProjectConfigurationPlatforms) = postSolution -\t\t.Release|Win32.ActiveCfg = Release|Win32 -\t\t.Release|Win32.Build.0 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(SolutionProperties) = preSolution -\t\tHideSolutionNode = FALSE -\tEndGlobalSection -EndGlobal -""" - -expected_slnfile_9_0 = """\ -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "" -EndProject -Global - -\tGlobalSection(SolutionConfigurationPlatforms) = preSolution -\t\tRelease|Win32 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(ProjectConfigurationPlatforms) = postSolution -\t\t.Release|Win32.ActiveCfg = Release|Win32 -\t\t.Release|Win32.Build.0 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(SolutionProperties) = preSolution -\t\tHideSolutionNode = FALSE -\tEndGlobalSection -EndGlobal -""" - -expected_slnfile_10_0 = """\ -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test.vcxproj", "Test.vcxproj", "{39A97E1F-1A52-8954-A0B1-A10A8487545E}" -EndProject -Global - -\tGlobalSection(SolutionConfigurationPlatforms) = preSolution -\t\tRelease|Win32 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(ProjectConfigurationPlatforms) = postSolution -\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.ActiveCfg = Release|Win32 -\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.Build.0 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(SolutionProperties) = preSolution -\t\tHideSolutionNode = FALSE -\tEndGlobalSection -EndGlobal -""" - -expected_slnfile_11_0 = """\ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 11 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test.vcxproj", "Test.vcxproj", "{39A97E1F-1A52-8954-A0B1-A10A8487545E}" -EndProject -Global - -\tGlobalSection(SolutionConfigurationPlatforms) = preSolution -\t\tRelease|Win32 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(ProjectConfigurationPlatforms) = postSolution -\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.ActiveCfg = Release|Win32 -\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.Build.0 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(SolutionProperties) = preSolution -\t\tHideSolutionNode = FALSE -\tEndGlobalSection -EndGlobal -""" - -expected_slnfile_14_0 = """\ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test.vcxproj", "Test.vcxproj", "{39A97E1F-1A52-8954-A0B1-A10A8487545E}" -EndProject -Global - -\tGlobalSection(SolutionConfigurationPlatforms) = preSolution -\t\tRelease|Win32 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(ProjectConfigurationPlatforms) = postSolution -\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.ActiveCfg = Release|Win32 -\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.Build.0 = Release|Win32 -\tEndGlobalSection -\tGlobalSection(SolutionProperties) = preSolution -\t\tHideSolutionNode = FALSE -\tEndGlobalSection -EndGlobal -""" - -expected_vcprojfile_8_0 = """\ - - -\tKeyword="MakeFileProj"> -\t -\t\t -\t -\t -\t -\t -\t\t -\t\t\t -\t\t -\t -\t -\t -\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t -\t -\t -\t - -""" - -expected_vcprojfile_9_0 = """\ - - -\tKeyword="MakeFileProj"> -\t -\t\t -\t -\t -\t -\t -\t\t -\t\t\t -\t\t -\t -\t -\t -\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t\t -\t\t\t -\t\t\t -\t\t\t -\t\t -\t\t -\t\t -\t -\t -\t - -""" - -expected_vcprojfile_10_0 = """\ - - -\t -\t\t -\t\t\tRelease -\t\t\tWin32 -\t\t -\t -\t -\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E} - -\t\tTest -\t\tMakeFileProj -\t -\t -\t -\t\tMakefile -\t\tfalse -\t\tv100 -\t -\t -\t -\t -\t -\t\t -\t -\t -\t -\t<_ProjectFileVersion>10.0.30319.1 -\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" -\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" -\t\techo Starting SCons && "" -c "" -C "" -f SConstruct -c "Test.exe" -\t\tTest.exe -\t\tDEF1;DEF2;DEF3=1234 -\t\tinc1;inc2 -\t\t$(NMakeForcedIncludes) -\t\t$(NMakeAssemblySearchPath) -\t\t$(NMakeForcedUsingAssemblies) -\t -\t -\t\t -\t -\t -\t\t -\t -\t -\t\t -\t -\t -\t\t -\t -\t -\t\t -\t\t -\t -\t -\t\t -\t -\t -\t -\t - -""" - -expected_vcprojfile_11_0 = """\ - - -\t -\t\t -\t\t\tRelease -\t\t\tWin32 -\t\t -\t -\t -\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E} - -\t\tTest -\t\tMakeFileProj -\t -\t -\t -\t\tMakefile -\t\tfalse -\t\tv110 -\t -\t -\t -\t -\t -\t\t -\t -\t -\t -\t<_ProjectFileVersion>10.0.30319.1 -\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" -\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" -\t\techo Starting SCons && "" -c "" -C "" -f SConstruct -c "Test.exe" -\t\tTest.exe -\t\tDEF1;DEF2;DEF3=1234 -\t\tinc1;inc2 -\t\t$(NMakeForcedIncludes) -\t\t$(NMakeAssemblySearchPath) -\t\t$(NMakeForcedUsingAssemblies) -\t -\t -\t\t -\t -\t -\t\t -\t -\t -\t\t -\t -\t -\t\t -\t -\t -\t\t -\t\t -\t -\t -\t\t -\t -\t -\t -\t - -""" - -expected_vcprojfile_14_0 = """\ - - -\t -\t\t -\t\t\tRelease -\t\t\tWin32 -\t\t -\t -\t -\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E} - -\t\tTest -\t\tMakeFileProj -\t -\t -\t -\t\tMakefile -\t\tfalse -\t\tv140 -\t -\t -\t -\t -\t -\t\t -\t -\t -\t -\t<_ProjectFileVersion>10.0.30319.1 -\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" -\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" -\t\techo Starting SCons && "" -c "" -C "" -f SConstruct -c "Test.exe" -\t\tTest.exe -\t\tDEF1;DEF2;DEF3=1234 -\t\tinc1;inc2 -\t\t$(NMakeForcedIncludes) -\t\t$(NMakeAssemblySearchPath) -\t\t$(NMakeForcedUsingAssemblies) -\t -\t -\t\t -\t -\t -\t\t -\t -\t -\t\t -\t -\t -\t\t -\t -\t -\t\t -\t\t -\t -\t -\t\t -\t -\t -\t -\t - -""" - -SConscript_contents_8_0 = """\ -env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='8.0', - CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')], - CPPPATH=['inc1', 'inc2'], - HOST_ARCH='%(HOST_ARCH)s') - -testsrc = ['test1.cpp', 'test2.cpp'] -testincs = ['sdk.h'] -testlocalincs = ['test.h'] -testresources = ['test.rc'] -testmisc = ['readme.txt'] - -env.MSVSProject(target = 'Test.vcproj', - slnguid = '{SLNGUID}', - srcs = testsrc, - incs = testincs, - localincs = testlocalincs, - resources = testresources, - misc = testmisc, - buildtarget = 'Test.exe', - variant = 'Release') -""" - -SConscript_contents_9_0 = """\ -env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='9.0', - CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')], - CPPPATH=['inc1', 'inc2'], - HOST_ARCH='%(HOST_ARCH)s') - -testsrc = ['test1.cpp', 'test2.cpp'] -testincs = ['sdk.h'] -testlocalincs = ['test.h'] -testresources = ['test.rc'] -testmisc = ['readme.txt'] - -env.MSVSProject(target = 'Test.vcproj', - slnguid = '{SLNGUID}', - srcs = testsrc, - incs = testincs, - localincs = testlocalincs, - resources = testresources, - misc = testmisc, - buildtarget = 'Test.exe', - variant = 'Release') -""" - -SConscript_contents_10_0 = """\ -env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='10.0', - CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')], - CPPPATH=['inc1', 'inc2'], - HOST_ARCH='%(HOST_ARCH)s') - -testsrc = ['test1.cpp', 'test2.cpp'] -testincs = ['sdk_dir\sdk.h'] -testlocalincs = ['test.h'] -testresources = ['test.rc'] -testmisc = ['readme.txt'] - -env.MSVSProject(target = 'Test.vcxproj', - slnguid = '{SLNGUID}', - srcs = testsrc, - incs = testincs, - localincs = testlocalincs, - resources = testresources, - misc = testmisc, - buildtarget = 'Test.exe', - variant = 'Release') -""" - -SConscript_contents_11_0 = """\ -env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='11.0', - CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')], - CPPPATH=['inc1', 'inc2'], - HOST_ARCH='%(HOST_ARCH)s') - -testsrc = ['test1.cpp', 'test2.cpp'] -testincs = ['sdk_dir\sdk.h'] -testlocalincs = ['test.h'] -testresources = ['test.rc'] -testmisc = ['readme.txt'] - -env.MSVSProject(target = 'Test.vcxproj', - slnguid = '{SLNGUID}', - srcs = testsrc, - incs = testincs, - localincs = testlocalincs, - resources = testresources, - misc = testmisc, - buildtarget = 'Test.exe', - variant = 'Release') -""" - -SConscript_contents_14_0 = """\ -env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='14.0', - CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')], - CPPPATH=['inc1', 'inc2'], - HOST_ARCH='%(HOST_ARCH)s') - -testsrc = ['test1.cpp', 'test2.cpp'] -testincs = ['sdk_dir\sdk.h'] -testlocalincs = ['test.h'] -testresources = ['test.rc'] -testmisc = ['readme.txt'] - -env.MSVSProject(target = 'Test.vcxproj', - slnguid = '{SLNGUID}', - srcs = testsrc, - incs = testincs, - localincs = testlocalincs, - resources = testresources, - misc = testmisc, - buildtarget = 'Test.exe', - variant = 'Release') -""" - -class TestSConsMSVS(TestSCons): - """Subclass for testing MSVS-specific portions of SCons.""" - - def msvs_versions(self): - if not hasattr(self, '_msvs_versions'): - - # Determine the SCons version and the versions of the MSVS - # environments installed on the test machine. - # - # We do this by executing SCons with an SConstruct file - # (piped on stdin) that spits out Python assignments that - # we can just exec(). We construct the SCons.__"version"__ - # string in the input here so that the SCons build itself - # doesn't fill it in when packaging SCons. - input = """\ -import SCons -import SCons.Tool.MSCommon -print("self.scons_version =%%s"%%repr(SCons.__%s__)) -print("self._msvs_versions =%%s"%%str(SCons.Tool.MSCommon.query_versions())) -""" % 'version' - - self.run(arguments = '-n -q -Q -f -', stdin = input) - exec(self.stdout()) - - return self._msvs_versions - - def vcproj_sys_path(self, fname): - """ - """ - orig = 'sys.path = [ join(sys' - - enginepath = repr(os.path.join(self._cwd, '..', 'engine')) - replace = 'sys.path = [ %s, join(sys' % enginepath - - contents = self.read(fname, mode='r') - contents = contents.replace(orig, replace) - self.write(fname, contents) - - def msvs_substitute(self, input, msvs_ver, - subdir=None, sconscript=None, - python=None, - project_guid=None, - vcproj_sccinfo='', sln_sccinfo=''): - if not hasattr(self, '_msvs_versions'): - self.msvs_versions() - - if subdir: - workpath = self.workpath(subdir) - else: - workpath = self.workpath() - - if sconscript is None: - sconscript = self.workpath('SConstruct') - - if python is None: - python = sys.executable - - if project_guid is None: - project_guid = "{E5466E26-0003-F18B-8F8A-BCD76C86388D}" - - if 'SCONS_LIB_DIR' in os.environ: - exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % os.environ['SCONS_LIB_DIR'] - else: - exec_script_main = "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-%s'), join(sys.prefix, 'scons-%s'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()" % (self.scons_version, self.scons_version) - exec_script_main_xml = exec_script_main.replace("'", "'") - - result = input.replace(r'', workpath) - result = result.replace(r'', python) - result = result.replace(r'', sconscript) - result = result.replace(r'', exec_script_main) - result = result.replace(r'', exec_script_main_xml) - result = result.replace(r'', project_guid) - result = result.replace('\n', vcproj_sccinfo) - result = result.replace('\n', sln_sccinfo) - return result - - def get_msvs_executable(self, version): - """Returns a full path to the executable (MSDEV or devenv) - for the specified version of Visual Studio. - """ - from SCons.Tool.MSCommon import get_vs_by_version - - msvs = get_vs_by_version(version) - if not msvs: - return None - return msvs.get_executable() - - def run(self, *args, **kw): - """ - Suppress MSVS deprecation warnings. - """ - save_sconsflags = os.environ.get('SCONSFLAGS') - if save_sconsflags: - sconsflags = [save_sconsflags] - else: - sconsflags = [] - sconsflags = sconsflags + ['--warn=no-deprecated'] - os.environ['SCONSFLAGS'] = ' '.join(sconsflags) - try: - result = TestSCons.run(self, *args, **kw) - finally: - os.environ['SCONSFLAGS'] = save_sconsflags or '' - return result - - def get_vs_host_arch(self): - """ Get an MSVS, SDK , and/or MSVS acceptable platform arch - """ - - # Dict to 'canonalize' the arch - _ARCH_TO_CANONICAL = { - "x86": "x86", - "amd64": "amd64", - "i386": "x86", - "emt64": "amd64", - "x86_64": "amd64", - "itanium": "ia64", - "ia64": "ia64", - } - - host_platform = platform.machine() - # TODO(2.5): the native Python platform.machine() function returns - # '' on all Python versions before 2.6, after which it also uses - # PROCESSOR_ARCHITECTURE. - if not host_platform: - host_platform = os.environ.get('PROCESSOR_ARCHITECTURE', '') - - - try: - host = _ARCH_TO_CANONICAL[host_platform] - except KeyError as e: - # Default to x86 for all other platforms - host = 'x86' - - - return host - - def validate_msvs_file(self, file): - try: - x = ElementTree.parse(file) - except: - print("--------------------------------------------------------------") - print("--------------------------------------------------------------") - print(traceback.format_exc()) - print("Failed to validate xml in MSVS file: ") - print(file) - print("--------------------------------------------------------------") - print("--------------------------------------------------------------") - self.fail_test() -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/QMTest/TestSCons_time.py b/QMTest/TestSCons_time.py deleted file mode 100644 index bc116ec..0000000 --- a/QMTest/TestSCons_time.py +++ /dev/null @@ -1,331 +0,0 @@ -""" -TestSCons_time.py: a testing framework for the scons-test.py script - -A TestSCons_time environment object is created via the usual invocation: - - test = TestSCons_time() - -TestSCons_time is a subclass of TestCommon, which is in turn is a subclass -of TestCmd), and hence has available all of the methods and attributes -from those classes, as well as any overridden or additional methods or -attributes defined in this subclass. -""" - -# __COPYRIGHT__ - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - -import os -import os.path -import sys - -from TestCommon import * -from TestCommon import __all__ -# some of the scons_time tests may need regex-based matching: -from TestSCons import search_re, search_re_in_list - -__all__.extend([ 'TestSCons_time', - ]) - -SConstruct = """\ -from __future__ import print_function -import os -print("SConstruct file directory:", os.getcwd()) -""" - -scons_py = """\ -#!/usr/bin/env python -import os -import sys -def write_args(fp, args): - fp.write(args[0] + '\\n') - for arg in args[1:]: - fp.write(' ' + arg + '\\n') -write_args(sys.stdout, sys.argv) -for arg in sys.argv[1:]: - if arg[:10] == '--profile=': - profile = open(arg[10:], 'w') - profile.write('--profile\\n') - write_args(profile, sys.argv) - break -sys.stdout.write('SCONS_LIB_DIR = ' + os.environ['SCONS_LIB_DIR'] + '\\n') -exec(open('SConstruct', 'r').read()) -""" - -aegis_py = """\ -#!/usr/bin/env python -import os -import sys -script_dir = 'src/script' -if not os.path.exists(script_dir): - os.makedirs(script_dir) -open(script_dir + '/scons.py', 'w').write( -r'''%s''') -""" % scons_py - - -svn_py = """\ -#!/usr/bin/env python -import os -import sys -dir = sys.argv[-1] -script_dir = dir + '/src/script' -os.makedirs(script_dir) -open(script_dir + '/scons.py', 'w').write( -r'''%s''') -""" % scons_py - - -logfile_contents = """\ -Memory before reading SConscript files: 100%(index)s -Memory after reading SConscript files: 200%(index)s -Memory before building targets: 300%(index)s -Memory after building targets: 400%(index)s -Object counts: - pre- post- pre- post- - read read build build Class - 101%(index)s 102%(index)s 103%(index)s 104%(index)s Action.CommandAction - 201%(index)s 202%(index)s 203%(index)s 204%(index)s Action.CommandGeneratorAction - 301%(index)s 302%(index)s 303%(index)s 304%(index)s Action.FunctionAction - 401%(index)s 402%(index)s 403%(index)s 404%(index)s Action.LazyAction - 501%(index)s 502%(index)s 503%(index)s 504%(index)s Action.ListAction - 601%(index)s 602%(index)s 603%(index)s 604%(index)s Builder.BuilderBase - 701%(index)s 702%(index)s 703%(index)s 704%(index)s Builder.CompositeBuilder - 801%(index)s 802%(index)s 803%(index)s 804%(index)s Builder.ListBuilder - 901%(index)s 902%(index)s 903%(index)s 904%(index)s Builder.MultiStepBuilder - 1001%(index)s 1002%(index)s 1003%(index)s 1004%(index)s Builder.OverrideWarner - 1101%(index)s 1102%(index)s 1103%(index)s 1104%(index)s Environment.Base - 1201%(index)s 1202%(index)s 1203%(index)s 1204%(index)s Environment.EnvironmentClone - 1301%(index)s 1302%(index)s 1303%(index)s 1304%(index)s Environment.OverrideEnvironment - 1401%(index)s 1402%(index)s 1403%(index)s 1404%(index)s Executor.Executor - 1501%(index)s 1502%(index)s 1503%(index)s 1504%(index)s Node.FS - 1601%(index)s 1602%(index)s 1603%(index)s 1604%(index)s Node.FS.Base - 1701%(index)s 1702%(index)s 1703%(index)s 1704%(index)s Node.FS.Dir - 1801%(index)s 1802%(index)s 1803%(index)s 1804%(index)s Node.FS.File - 1901%(index)s 1902%(index)s 1904%(index)s 1904%(index)s Node.FS.RootDir - 2001%(index)s 2002%(index)s 2003%(index)s 2004%(index)s Node.Node -Total build time: 11.123456 seconds -Total SConscript file execution time: 22.234567 seconds -Total SCons execution time: 33.345678 seconds -Total command execution time: 44.456789 seconds -""" - - -profile_py = """\ -%(body)s - -import profile - -try: dispatch = profile.Profile.dispatch -except AttributeError: pass -else: dispatch['c_exception'] = profile.Profile.trace_dispatch_return - -prof = profile.Profile() -prof.runcall(%(call)s) -prof.dump_stats(r'%(profile_name)s') -""" - - -class TestSCons_time(TestCommon): - """Class for testing the scons-time script. - - This provides a common place for initializing scons-time tests, - eliminating the need to begin every test with the same repeated - initializations. - """ - - def __init__(self, **kw): - """Initialize an SCons_time testing object. - - If they're not overridden by keyword arguments, this - initializes the object with the following default values: - - program = 'scons-time' - interpreter = ['python', '-tt'] - match = match_exact - workdir = '' - - The workdir value means that, by default, a temporary workspace - directory is created for a TestSCons_time environment. - In addition, this method changes directory (chdir) to the - workspace directory, so an explicit "chdir = '.'" on all of the - run() method calls is not necessary. - """ - - self.orig_cwd = os.getcwd() - try: - script_dir = os.environ['SCONS_SCRIPT_DIR'] - except KeyError: - pass - else: - os.chdir(script_dir) - if 'program' not in kw: - p = os.environ.get('SCONS_TIME') - if not p: - p = 'scons-time' - if not os.path.exists(p): - p = 'scons-time.py' - kw['program'] = p - - if 'interpreter' not in kw: - kw['interpreter'] = [python, '-tt'] - - if 'match' not in kw: - kw['match'] = match_exact - - if 'workdir' not in kw: - kw['workdir'] = '' - - TestCommon.__init__(self, **kw) - - def archive_split(self, path): - if path[-7:] == '.tar.gz': - return path[:-7], path[-7:] - else: - return os.path.splitext(path) - - def fake_logfile(self, logfile_name, index=0): - self.write(self.workpath(logfile_name), logfile_contents % locals()) - - def profile_data(self, profile_name, python_name, call, body): - profile_name = self.workpath(profile_name) - python_name = self.workpath(python_name) - d = { - 'profile_name' : profile_name, - 'python_name' : python_name, - 'call' : call, - 'body' : body, - } - self.write(python_name, profile_py % d) - self.run(program = python_name, interpreter = sys.executable) - - def tempdir_re(self, *args): - """ - Returns a regular expression to match a scons-time - temporary directory. - """ - import re - import tempfile - - sep = re.escape(os.sep) - tempdir = tempfile.gettempdir() - - try: - realpath = os.path.realpath - except AttributeError: - pass - else: - tempdir = realpath(tempdir) - - args = (tempdir, 'scons-time-',) + args - x = os.path.join(*args) - x = re.escape(x) - x = x.replace('time\\-', 'time\\-[^%s]*' % sep) - return x - - def write_fake_aegis_py(self, name): - name = self.workpath(name) - self.write(name, aegis_py) - os.chmod(name, 0o755) - return name - - def write_fake_scons_py(self): - self.subdir('src', ['src', 'script']) - self.write('src/script/scons.py', scons_py) - - def write_fake_svn_py(self, name): - name = self.workpath(name) - self.write(name, svn_py) - os.chmod(name, 0o755) - return name - - def write_sample_directory(self, archive, dir, files): - dir = self.workpath(dir) - for name, content in files: - path = os.path.join(dir, name) - d, f = os.path.split(path) - if not os.path.isdir(d): - os.makedirs(d) - open(path, 'w').write(content) - return dir - - def write_sample_tarfile(self, archive, dir, files): - import shutil - try: - import tarfile - - except ImportError: - - self.skip_test('no tarfile module\n') - - else: - - base, suffix = self.archive_split(archive) - - mode = { - '.tar' : 'w', - '.tar.gz' : 'w:gz', - '.tgz' : 'w:gz', - } - - tar = tarfile.open(archive, mode[suffix]) - for name, content in files: - path = os.path.join(dir, name) - open(path, 'wb').write(bytearray(content,'utf-8')) - tarinfo = tar.gettarinfo(path, path) - tarinfo.uid = 111 - tarinfo.gid = 111 - tarinfo.uname = 'fake_user' - tarinfo.gname = 'fake_group' - tar.addfile(tarinfo, open(path, 'rb')) - tar.close() - shutil.rmtree(dir) - return self.workpath(archive) - - def write_sample_zipfile(self, archive, dir, files): - import shutil - try: - import zipfile - except ImportError: - - sys.stderr.write('no zipfile module\n') - self.no_result() - - else: - - zip = zipfile.ZipFile(archive, 'w') - for name, content in files: - path = os.path.join(dir, name) - open(path, 'w').write(content) - zip.write(path) - zip.close() - shutil.rmtree(dir) - return self.workpath(archive) - - sample_project_files = [ - ('SConstruct', SConstruct), - ] - - def write_sample_project(self, archive, dir=None): - base, suffix = self.archive_split(archive) - - write_sample = { - '.tar' : self.write_sample_tarfile, - '.tar.gz' : self.write_sample_tarfile, - '.tgz' : self.write_sample_tarfile, - '.zip' : self.write_sample_zipfile, - }.get(suffix, self.write_sample_directory) - - if not dir: - dir = base - - os.mkdir(dir) - path = write_sample(archive, dir, self.sample_project_files) - - return path - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/QMTest/TestSConsign.py b/QMTest/TestSConsign.py deleted file mode 100644 index a48b648..0000000 --- a/QMTest/TestSConsign.py +++ /dev/null @@ -1,90 +0,0 @@ -# __COPYRIGHT__ -from __future__ import print_function - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - -__doc__ = """ -TestSConsign.py: a testing framework for the "sconsign" script -tool. - -A TestSConsign environment object is created via the usual invocation: - - test = TestSConsign() - -TestSconsign is a subclass of TestSCons, which is a subclass of -TestCommon, which is in turn is a subclass of TestCmd), and hence -has available all of the methods and attributes from those classes, -as well as any overridden or additional methods or attributes defined -in this subclass. -""" - -import os -import os.path -import sys - -from TestSCons import * -from TestSCons import __all__ - -__all__.extend([ 'TestSConsign', ]) - -class TestSConsign(TestSCons): - """Class for testing the sconsign.py script. - - This provides a common place for initializing sconsign tests, - eliminating the need to begin every test with the same repeated - initializations. - - This adds additional methods for running the sconsign script - without changing the basic ability of the run() method to run - "scons" itself, since we need to run scons to generate the - .sconsign files that we want the sconsign script to read. - """ - def __init__(self, *args, **kw): - try: - script_dir = os.environ['SCONS_SCRIPT_DIR'] - except KeyError: - pass - else: - os.chdir(script_dir) - self.script_dir = os.getcwd() - - TestSCons.__init__(self, *args, **kw) - - self.my_kw = { - 'interpreter' : python, # imported from TestSCons - } - - if 'program' not in kw: - kw['program'] = os.environ.get('SCONS') - if not kw['program']: - if os.path.exists('scons'): - kw['program'] = 'scons' - else: - kw['program'] = 'scons.py' - - sconsign = os.environ.get('SCONSIGN') - if not sconsign: - if os.path.exists(self.script_path('sconsign.py')): - sconsign = 'sconsign.py' - elif os.path.exists(self.script_path('sconsign')): - sconsign = 'sconsign' - else: - print("Can find neither 'sconsign.py' nor 'sconsign' scripts.") - self.no_result() - self.set_sconsign(sconsign) - - def script_path(self, script): - return os.path.join(self.script_dir, script) - - def set_sconsign(self, sconsign): - self.my_kw['program'] = sconsign - - def run_sconsign(self, *args, **kw): - kw.update(self.my_kw) - return self.run(*args, **kw) - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/QMTest/test-framework.rst b/QMTest/test-framework.rst deleted file mode 100644 index d51e5f0..0000000 --- a/QMTest/test-framework.rst +++ /dev/null @@ -1,430 +0,0 @@ -======================= -SCons Testing Framework -======================= - -SCons uses extensive automated tests to try to ensure quality. The primary goal -is that users should be able to upgrade from version to version without any surprise -changes in behavior. - -In general, no change goes into SCons unless it has one or more new or modified -tests that demonstrably exercise the bug being fixed or the feature being added. -There are exceptions to this guideline, but they should be just that, ''exceptions''. -When in doubt, make sure it's tested. - -Test Organization -================= - -There are three types of SCons tests: - -*End-to-End Tests* - End-to-end tests of SCons are all Python scripts (``*.py``) underneath - the ``test/`` subdirectory. They use the test infrastructure modules in the - ``QMTest`` subdirectory. - -*Unit Tests* - Unit tests for individual SCons modules live underneath the - ``src/engine/`` subdirectory and are the same base name as the module - with ``Tests.py`` appended--for example, the unit tests for the - ``Builder.py`` module are in the ``BuilderTests.py`` script. - -*External Tests* - For the support of external Tools (in the form of packages, preferably), the - testing framework got extended, such that it can run in standalone mode. - You can start it from the top-level folder of your Tool's source tree, - where it then finds all Python scripts (``*.py``) underneath the - local ``test/`` directory. - This implies that Tool tests have to be kept in a folder named ``test``, - like for the SCons core. - - -Contrasting End-to-End and Unit Tests -##################################### - -In general, anything that we've put into an end-to-end test script should -be considered a hardened part of the interface (that is, it's something -that a user might do) and should not be broken. Unit tests are now -considered more malleable, more for testing internal interfaces that -can change so long as we don't break users' ``SConscript`` files. (This -wasn't always the case, and there's a lot of meaty code in many of the -unit test scripts that does, in fact, capture external interface -behavior. In general, we should try to move those things to end-to-end -scripts as we find them.) - -It's more difficult to debug end-to-end tests. You can actually go -straight into the Python debugger on the unit test scripts by using the -``runtest.py --pdb`` option, but the end-to-end tests treat an SCons -invocation as a "black box" and just look for external effects. -Simple ``print`` statements within the SCons code itself often don't help -debug end-to-end because they end up in SCons output that gets compared -against expected output and cause a test failure. Probably the most -effective technique is to use the internal ``SCons.Debug.Trace()`` function, -which prints output to ``/dev/tty`` on Linux/UNIX systems and ``con`` on -Windows systems, so you can see what's going on. - -Naming conventions -################## - -The end-to-end tests, more or less, stick to the following naming conventions: - -1. All tests end with a .py suffix. - -2. In the *General* form we use - - ``Feature.py`` - for the test of a specified feature; try to - keep this description reasonably short - - ``Feature-x.py`` - for the test of a specified feature using - option ``x`` - -3. The *command line option* tests take the form - - ``option-x.py`` - for a lower-case single-letter option - - ``option--X.py`` - upper-case single-letter option - (with an extra hyphen, so the file names will - be unique on case-insensitive systems) - - ``option--lo.py`` - long option; abbreviate the long - option name to a few characters - - -Running Tests -============= - -The standard set of SCons tests are run from the top-level source directory -by the ``runtest.py`` script. -There is a ``--qmtest`` option that checks whether the ``QMTest`` package -is installed on your system. If it can be found, then the ``runtest.py`` script -will use it to carry out the tests. - -Help is available through the ``-h`` option: - -:: - - $ python runtest.py -h - -To simply run all the tests, use the ``-a`` option: - -:: - - $ python runtest.py -a - -By default, ``runtest.py`` prints a count and percentage message for each test -case, along with the name of the test file. -If you need the output to be more silent, have a look at the ``-q``, ``-s`` and -``-k`` options. - -You may specifically list one or more tests to be run: - -:: - - $ python runtest.py src/engine/SCons/BuilderTests.py - $ python runtest.py test/option-j.py test/Program.py - -Folder names are allowed arguments as well, so you can do a - -:: - - $ python runtest.py test/SWIG - -to run all SWIG tests only. - -You can also use the ``-f`` option to execute just the tests listed in a specified -text file: - -:: - - $ cat testlist.txt - test/option-j.py - test/Program.py - $ python runtest.py -f testlist.txt - - -One test must be listed per line, and any lines that begin with '#' -will be ignored (the intent being to allow you, for example, -to comment out tests that -are currently passing and then uncomment all of the tests in the file -for a final validation run). - -If more than one test is run, the ``runtest.py`` script prints a summary -of how many tests passed, failed, or yielded no result, and lists any -unsuccessful tests. - -The above invocations all test directly the files underneath the ``src/`` -subdirectory, and do not require that a packaging build be performed first. -The ``runtest.py`` script supports additional options to run tests against -unpacked packages in the ``build/test-*/`` subdirectories. - -If you are testing a separate Tool outside of the SCons source tree, you have -to call the ``runtest.py`` script in *external* (stand-alone) mode:: - - $ python ~/scons/runtest.py -e -a - -. This ensures that the testing framework doesn't try to access SCons classes -needed for some of the *internal* test cases. - -Note, that the actual tests are carried out in a temporary folder each, which gets -deleted afterwards. This ensures that your source directories don't get clobbered -with temporary files from the test runs. It also means that you can't simply change -into a folder to "debug things" after a test has gone wrong. For a way around this, -check out the ``PRESERVE`` environment variable. It can be seen in action in -`How to convert old tests`_ below. - -Not Running Tests -================= - -If you simply want to check which tests would get executed, you can call the -``runtest.py`` script with the ``-l`` option:: - - $ python runtest.py -l - -Then there is also the ``-n`` option, which prints the command line for each -single test, but doesn't actually execute them:: - - $ python runtest.py -n - -Finding Tests -============= - -When started in *standard* mode - -:: - - $ python runtest.py -a - - -, ``runtest.py`` assumes that it is run from the SCons top-level source directory. -It then dives into the ``src`` and ``test`` folders, where it tries to find filenames - - ``*Test.py`` - for the ``src`` directory, and - - ``*.py`` - for the ``test`` folder. - -When using fixtures, you may quickly end up in a position where you have supporting -Python script files in a subfolder, but they shouldn't get picked up as test scripts. -In this case you have two options: - -1. Add a file with the name ``sconstest.skip`` to your subfolder. This lets - ``runtest.py`` skip the contents of the directory completely. -2. Create a file ``.exclude_tests`` in each folder in question, and in it list - line-by-line the files to get excluded from testing. - -The same rules apply when testing external Tools by using the ``-e`` option. - - -"Hello, world!" SCons Test Script -================================= - -To illustrate how the end-to-end test scripts work, -let's walk through a simple "Hello, world!" example: - -:: - - #!python - import TestSCons - - test = TestSCons.TestSCons() - - test.write('SConstruct', """\ - Program('hello.c') - """) - - test.write('hello.c', """\ - int - main(int argc, char *argv[]) - { - printf("Hello, world!\\n"); - exit (0); - } - """) - - test.run() - - test.run(program='./hello', stdout="Hello, world!\n") - - test.pass_test() - - -``import TestSCons`` - Imports the main infrastructure for writing SCons tests. This is normally the only part of the infrastructure that needs importing. Sometimes other Python modules are necessary or helpful, and get imported before this line. - -``test = TestSCons.TestSCons()`` - This initializes an object for testing. A fair amount happens under the covers when the object is created, including: - - * A temporary directory is created for all the in-line files that will get created. - * The temporary directory's removal is arranged for when the test is finished. - * We ``os.chdir()`` to the temporary directory. - -``test.write('SConstruct', ...`` - This line creates an ``SConstruct`` file in the temporary directory, to be used as input to the ``scons`` run(s) that we're testing. Note the use of the Python triple-quote syntax for the contents of the ``SConstruct`` file. Because input files for tests are all created from in-line data like this, the tests can sometimes get a little confusing to read, because some of the Python code is found - -``test.write('hello.c', ...`` - This lines creates an ``hello.c`` file in the temporary directory. Note that we have to escape the ``\\n`` in the ``"Hello, world!\\n"`` string so that it ends up as a single backslash in the ``hello.c`` file on disk. - -``test.run()`` - This actually runs SCons. Like the object initialization, things happen under the covers: - - * The exit status is verified; the test exits with a failure if the exit status is not zero. - * The error output is examined, and the test exits with a failure if there is any - -``test.run(program='./hello', stdout="Hello, world!\n")`` - This shows use of the ``TestSCons.run()`` method to execute a program other than ``scons``, in this case the ``hello`` program we just presumably built. The ``stdout=`` keyword argument also tells the ``TestSCons.run()`` method to fail if the program output does not match the expected string ``"Hello, world!\n"``. Like the previous ``test.run()`` line, it will also fail the test if the exit status is non-zero, or there is any error output. - -``test.pass_test()`` - This is always the last line in a test script. It prints ``PASSED`` on the screen and makes sure we exit with a ``0`` status to indicate the test passed. As a side effect of destroying the ``test`` object, the created temporary directory will be removed. - -Working with fixtures -===================== - -In the simple example above, we have seen how to create files in the temporary test directory. -We give a filename to the ``TestSCons.write()`` method, together with its contents, and it gets -written to the test folder right before its start. - -This technique can still be seen throughout most of the end-to-end tests, but there is a better -way. It's much easier to edit, create and maintain real files, instead of copy/pasting -content to/from a Python script. If the test files get longer, the test script -gets longer and is harder to read. - -Against this, we now have the possibility to copy single files or the contents of a -local folder to the test directory. Since we can reuse these files/folders to setup -several tests, we call them *fixtures* in the following. - -Directory fixtures -################## - -The function ``dir_fixture(self, srcdir, dstdir=None)`` in the ``TestCmd`` class -copies the contents of the specified folder ``srcdir`` from -the directory of the called test script, to the current -temporary test directory. -The ``srcdir`` name may be a list, in which case the elements are -concatenated with the ``os.path.join()`` method. The ``dstdir`` is -assumed to be under the temporary working directory, it gets -created automatically, if it does not already exist. - -A short syntax example:: - - test = TestSCons.TestSCons() - test.dir_fixture('image') - test.run() - -would copy all files and subfolders from the local ``image`` folder, to -the temporary directory for the current test. - -If you'd like to see a real example for this in action, refer to the test -named ``test/packaging/convenience-functions/convenience-functions.py``. - -File fixtures -############# - -Like for directory fixtures, ``file_fixture(self, srcfile, dstfile=None)`` -copies the file ``srcfile`` from the directory of -the called script, to the temporary test directory. -The ``dstfile`` is assumed to be under the temporary working -directory, unless it is an absolute path name. -If ``dstfile`` is specified, its target directory gets created -automatically if it doesn't already exist. - -With a:: - - test = TestSCons.TestSCons() - test.file_fixture('SConstruct') - test.file_fixture(['src','main.cpp'],['src','main.cpp']) - test.run() - -you would copy the files ``SConstruct`` and ``src/main.cpp`` to the temporary -test folder, prior to running the test itself. - -Again, a reference example can be found in the current *default* revision of -SCons, it is ``test/packaging/sandbox-test/sandbox-test.py``. - -For even more examples you should check out one of the external Tools, e.g. the -*Qt4* Tool at https://bitbucket.org/dirkbaechle/scons_qt4. Also visit the SCons -Tools Index at https://github.com/SCons/scons/wiki/ToolsIndex for a complete -list of available Tools, though not all may have tests yet. - -How to convert old tests -######################## - -We now show how to convert a test, still using the ``TestSCons.write()`` method, to -the fixture based approach. For this, we need to get at the files as they -are written to each temporary test folder. - -Luckily, ``runtest.py`` checks for the existence of an environment variable named -``PRESERVE``. If it is set to a non-zero value, the testing framework doesn't delete -the test folder as ususal, but prints its name to the screen. - -So, you should be able to give the commands - -:: - - $ export PRESERVE=1 - $ python runtest.py test/packaging/sandbox-test.py - -, assuming Linux and a bash-like shell. - -The output should then look something like this:: - - 1/1 (100.00%) /usr/bin/python -tt test/packaging/sandbox-test.py - PASSED - Preserved directory /tmp/testcmd.4060.twlYNI - -and you see that the test files have been kept in the folder ``/tmp/testcmd.4060.twlYNI``, -where you can now copy them from to your new *fixture* folder. Then, in the test -script you simply remove all the tedious ``TestSCons.write()`` statements and -replace them by a single ``TestSCons.dir_fixture()``. - -Finally, you shouldn't forget to clean up and remove the temporary test directory. ``;)`` - -Test Infrastructure -=================== - -The test API is in ``QMTest/TestSCons.py``. ``TestSCons`` is a subclass of -``TestCommon``, which is a subclass of ``TestCmd``; all those python files are -in ``QMTest``. Start in ``QMTest/TestCmd.py`` for the base API definitions, -like how to create files (``test.write()``) and run commands (``test.run()``). - -You want to use ``TestSCons`` for the end-to-end tests in ``test``, but ``TestCmd`` -for the unit tests in the ``src`` folder. - -The match functions work like this: - -TestSCons.match_re:: match each line with a RE - * Splits the lines into a list (unless they already are) - * splits the REs at newlines (unless already a list) and puts ^..$ around each - * then each RE must match each line. This means there must be as many REs as lines. - -TestSCons.match_re_dotall:: match all the lines against a single RE - * Joins the lines with newline (unless already a string) - * joins the REs with newline (unless it's a string) and puts ^..$ around the whole thing - * then whole thing must match with python re.DOTALL. - -Use them in a test like this:: - - test.run(..., match=TestSCons.match_re, ...) - -or:: - - test.must_match(..., match=TestSCons.match_re, ...) - -Avoiding Tests based on Tool existence -====================================== - -Here's an easy sample:: - - #!python - intelc = test.detect_tool('intelc', prog='icpc') - if not intelc: - test.skip_test("Could not load 'intelc' Tool; skipping test(s).\n") - -See ``QMTest/TestSCons.py`` for the ``detect_tool`` method. It calls the tool's -``generate()`` method, and then looks for the given prog (tool name by default) in -``env['ENV']['PATH']``. - - diff --git a/SConstruct b/SConstruct index 1a0deb3..fc94abd 100644 --- a/SConstruct +++ b/SConstruct @@ -132,7 +132,7 @@ python_ver = sys.version[0:3] # for the doc toolchain. # addpaths = [os.path.abspath(os.path.join(os.getcwd(), 'bin')), - os.path.abspath(os.path.join(os.getcwd(), 'QMTest'))] + os.path.abspath(os.path.join(os.getcwd(), 'testing/framework'))] for a in addpaths: if a not in sys.path: sys.path.append(a) @@ -634,7 +634,7 @@ for p in [ scons ]: # Run setup.py in the unpacked subdirectory to "install" everything # into our build/test subdirectory. The runtest.py script will set # PYTHONPATH so that the tests only look under build/test-{package}, - # and under QMTest (for the testing modules TestCmd.py, TestSCons.py, + # and under testing/framework (for the testing modules TestCmd.py, TestSCons.py, # etc.). This makes sure that our tests pass with what # we really packaged, not because of something hanging around in # the development directory. @@ -704,7 +704,7 @@ for p in [ scons ]: # Run setup.py in the unpacked subdirectory to "install" everything # into our build/test subdirectory. The runtest.py script will set # PYTHONPATH so that the tests only look under build/test-{package}, - # and under QMTest (for the testing modules TestCmd.py, TestSCons.py, + # and under testing/framework (for the testing modules TestCmd.py, TestSCons.py, # etc.). This makes sure that our tests pass with what # we really packaged, not because of something hanging around in # the development directory. @@ -825,7 +825,7 @@ for p in [ scons ]: # Export('build_dir', 'env') -SConscript('QMTest/SConscript') +SConscript('testing/SConscript') # # @@ -926,7 +926,7 @@ if sfiles: # Run setup.py in the unpacked subdirectory to "install" everything # into our build/test subdirectory. The runtest.py script will set # PYTHONPATH so that the tests only look under build/test-{package}, - # and under QMTest (for the testing modules TestCmd.py, + # and under testing/framework (for the testing modules TestCmd.py, # TestSCons.py, etc.). This makes sure that our tests pass with # what we really packaged, not because of something hanging around # in the development directory. @@ -980,7 +980,7 @@ if sfiles: # Run setup.py in the unpacked subdirectory to "install" everything # into our build/test subdirectory. The runtest.py script will set # PYTHONPATH so that the tests only look under build/test-{package}, - # and under QMTest (for the testing modules TestCmd.py, + # and under testing/framework (for the testing modules TestCmd.py, # TestSCons.py, etc.). This makes sure that our tests pass with # what we really packaged, not because of something hanging # around in the development directory. @@ -1018,7 +1018,7 @@ if sfiles: for pf, help_text in packaging_flavors: Alias(pf, [ os.path.join(build_dir, 'test-'+pf), - os.path.join(build_dir, 'QMTest'), + os.path.join(build_dir, 'testing/framework'), os.path.join(build_dir, 'runtest.py'), ]) diff --git a/bin/SConsExamples.py b/bin/SConsExamples.py index a2ed570..7491c58 100644 --- a/bin/SConsExamples.py +++ b/bin/SConsExamples.py @@ -416,8 +416,8 @@ def exampleNamesAreUnique(dpath): # # ############################################################### -sys.path.append(os.path.join(os.getcwd(), 'QMTest')) -sys.path.append(os.path.join(os.getcwd(), 'build', 'QMTest')) +sys.path.append(os.path.join(os.getcwd(), 'testing/framework')) +sys.path.append(os.path.join(os.getcwd(), 'build', 'testing/framework')) scons_py = os.path.join('bootstrap', 'src', 'script', 'scons.py') if not os.path.exists(scons_py): diff --git a/bin/time-scons.py b/bin/time-scons.py index b7d8ef1..c5cd0cc 100644 --- a/bin/time-scons.py +++ b/bin/time-scons.py @@ -43,7 +43,7 @@ TimeSCons_revision = 4569 # The pieces of the TimeSCons infrastructure that are necessary to # produce consistent timings, even when the rest of the tree is from # an earlier revision that doesn't have these pieces. -TimeSCons_pieces = ['QMTest', 'timings', 'runtest.py'] +TimeSCons_pieces = ['testing/framework', 'timings', 'runtest.py'] class CommandRunner(object): diff --git a/bin/update-release-info.py b/bin/update-release-info.py index 81e0df5..5b871cb 100644 --- a/bin/update-release-info.py +++ b/bin/update-release-info.py @@ -20,10 +20,10 @@ in various files: - The RELEASE header line in src/CHANGES.txt and src/Announce.txt. - The version string at the top of src/RELEASE.txt. - The version string in the 'default_version' variable in SConstruct - and QMTest/TestSCons.py. - - The copyright years in SConstruct and QMTest/TestSCons.py. + and testing/framework/TestSCons.py. + - The copyright years in SConstruct and testing/framework/TestSCons.py. - The month and year (used for documentation) in SConstruct. - - The unsupported and deprecated Python floors in QMTest/TestSCons.py + - The unsupported and deprecated Python floors in testing/framework/TestSCons.py and src/engine/SCons/Script/Main.py - The version string in the filenames in README. @@ -315,9 +315,9 @@ for suf in ['tar', 'win32', 'zip', 'rpm', 'exe', 'deb']: '-%s.%s' % (version_string, suf), count = 0) -# Update QMTest/TestSCons.py +# Update testing/framework/TestSCons.py -t = UpdateFile(os.path.join('QMTest', 'TestSCons.py')) +t = UpdateFile(os.path.join('testing','framework', 'TestSCons.py')) if DEBUG: t.file = '/tmp/TestSCons.py' t.replace_assign('copyright_years', repr(copyright_years)) t.replace_assign('default_version', repr(version_string)) diff --git a/runtest.py b/runtest.py index cf8925c..e5c5f2f 100755 --- a/runtest.py +++ b/runtest.py @@ -8,9 +8,9 @@ # # - unit tests - included in *Tests.py files from src/ dir # - end-to-end tests - these are *.py files in test/ directory that -# require custom SCons framework from QMTest/ +# require custom SCons framework from testing/ # -# This script adds src/ and QMTest/ directories to PYTHONPATH, +# This script adds src/ and testing/ directories to PYTHONPATH, # performs test discovery and processes them according to options. # # With -p (--package) option, script tests specified package from @@ -625,10 +625,8 @@ old_pythonpath = os.environ.get('PYTHONPATH') # should be able to go away. pythonpaths = [ pythonpath_dir ] -# Add path of the QMTest folder to PYTHONPATH -# [ ] move used parts from QMTest to testing/framework/ scriptpath = os.path.dirname(os.path.realpath(__file__)) -pythonpaths.append(os.path.join(scriptpath, 'QMTest')) + # Add path for testing framework to PYTHONPATH pythonpaths.append(os.path.join(scriptpath, 'testing', 'framework')) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index ee4d7bb..a4590d0 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -35,6 +35,8 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - Fix issue # 3106 MSVC if using MSVC_BATCH and target dir had a space would fail due to quirk in MSVC's handling of escaped targetdirs when batch compiling. - Re-Enable parallel SCons (-j) when running via Pypy + - Move SCons test framework files to testing/framework and remove all references to QMtest. + QMTest has not been used by SCons for some time now. From Andrew Featherstone - Removed unused --warn options from the man page and source code. diff --git a/src/test_strings.py b/src/test_strings.py index 297d59d..e9f0abf 100644 --- a/src/test_strings.py +++ b/src/test_strings.py @@ -208,12 +208,10 @@ check_list = [ 'doc/user/SCons-win32-install-4.jpg', 'examples', 'gentoo', - 'QMTest/classes.qmc', - 'QMTest/configuration', - 'QMTest/TestCmd.py', - 'QMTest/TestCmdTests.py', - 'QMTest/TestCommon.py', - 'QMTest/TestCommonTests.py', + 'testing/framework/TestCmd.py', + 'testing/framework/TestCmdTests.py', + 'testing/framework/TestCommon.py', + 'testing/framework/TestCommonTests.py', 'src/MANIFEST.in', 'src/setup.cfg', 'src/engine/MANIFEST.in', diff --git a/test/D/Support/executablesSearch.py b/test/D/Support/executablesSearch.py index 17d9990..c572f56 100755 --- a/test/D/Support/executablesSearch.py +++ b/test/D/Support/executablesSearch.py @@ -51,7 +51,7 @@ def isExecutableOfToolAvailable(test, tool): if __name__ == '__main__': import unittest - sys.path.append(os.path.abspath('../../../QMTest')) + sys.path.append(os.path.abspath('../../../testing/framework')) import TestSCons diff --git a/test/README b/test/README index 2c8423f..be75398 100644 --- a/test/README +++ b/test/README @@ -4,7 +4,7 @@ This directory contains our end-to-end SCons tests. They are all meant to be run essentially standalone, with the exception of the TestSCons.py module and the other modules imported by the various -tests. These modules are in the QMTest/ subdirectory, and PYTHONPATH +tests. These modules are in the testing/framework/ subdirectory, and PYTHONPATH needs to be set up correctly so that the test scripts can find them, and so that the SCons script itself can find the build engine modules. @@ -56,4 +56,4 @@ semblance of uniformity, here are the naming conventions for tests: For some more information about running the tests and writing them, please refer to the documentation for the testing framework. It can be found in -the 'QMTest' folder, as file 'test-framework.rst'. +the 'testing/framework' folder, as file 'test-framework.rst'. diff --git a/test/update-release-info/update-release-info.py b/test/update-release-info/update-release-info.py index 970bcce..d0242a8 100644 --- a/test/update-release-info/update-release-info.py +++ b/test/update-release-info/update-release-info.py @@ -34,7 +34,7 @@ import TestRuntest # Needed to ensure we're using the correct year this_year=time.localtime()[0] -TestSCons = 'QMTest/TestSCons.py' .split('/') +TestSCons = 'testing/framework/TestSCons.py' .split('/') README = 'README.rst' .split('/') ReleaseConfig = 'ReleaseConfig' .split('/') SConstruct = 'SConstruct' .split('/') diff --git a/testing/framework/README.txt b/testing/framework/README.txt new file mode 100644 index 0000000..7750bc7 --- /dev/null +++ b/testing/framework/README.txt @@ -0,0 +1,50 @@ +This directory contains testing infrastructure. Note that not all of +the pieces here are local to SCons. + + README.txt + + What you're looking at right now. + + SConscript + + Configuration for our packaging build, to copy the necessary + parts of the infrastructure into a build directory. + + TestCmd.py + TestCmdTests.py + TestCommon.py + TestCommonTests.py + + The TestCmd infrastructure for testing external commands. + These are for generic command testing, are used by some + other projects, and are developed separately from SCons. + (They're developed by SK, but still...) + + We've captured the unit tests (Test*Tests.py) for these files + along with the actual modules themselves to make it a little + easier to hack on them for our purposes. Note, however, + that any SCons-specific functionality should be implemented + in one of the + + TestRuntest.py + + Test infrastructure for our runtest.py script. + + TestSCons.py + + Test infrastructure for SCons itself. + + TestSConsMSVS.py + + Test infrastructure for SCons' Visual Studio support. + + TestSCons_time.py + + Test infrastructure for the scons-time.py script. + + TestSConsign.py + + Test infrastructure for the sconsign.py script. + +__COPYRIGHT__ +__FILE__ __REVISION__ __DATE__ __DEVELOPER__ diff --git a/testing/framework/SConscript b/testing/framework/SConscript new file mode 100644 index 0000000..0d3832e --- /dev/null +++ b/testing/framework/SConscript @@ -0,0 +1,61 @@ +# +# SConscript file for external packages we need. +# + +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +import os.path + +Import('build_dir', 'env') + +files = [ + 'TestCmd.py', + 'TestCommon.py', + 'TestRuntest.py', + 'TestSCons.py', + 'TestSConsign.py', + 'TestSCons_time.py', +] + +def copy(target, source, env): + t = str(target[0]) + s = str(source[0]) + c = open(s, 'r').read() + # Note: We construct the __ VERSION __ substitution string at + # run-time so it doesn't get replaced when this file gets copied + # into the tree for packaging. + c = c.replace('__' + 'VERSION' + '__', env['VERSION']) + open(t, 'w').write(c) + +for file in files: + # Guarantee that real copies of these files always exist in + # build/testing/framework. If there's a symlink there, then this is an Aegis + # build and we blow them away now so that they'll get "built" later. + p = os.path.join(build_dir, 'testing','framework', file) + if os.path.islink(p): + os.unlink(p) + if not os.path.isabs(p): + p = '#' + p + env.Command(p, file, copy) + Local(p) diff --git a/testing/framework/TestCmd.py b/testing/framework/TestCmd.py new file mode 100644 index 0000000..0aab9a8 --- /dev/null +++ b/testing/framework/TestCmd.py @@ -0,0 +1,1907 @@ +""" +TestCmd.py: a testing framework for commands and scripts. + +The TestCmd module provides a framework for portable automated testing +of executable commands and scripts (in any language, not just Python), +especially commands and scripts that require file system interaction. + +In addition to running tests and evaluating conditions, the TestCmd +module manages and cleans up one or more temporary workspace +directories, and provides methods for creating files and directories in +those workspace directories from in-line data, here-documents), allowing +tests to be completely self-contained. + +A TestCmd environment object is created via the usual invocation: + + import TestCmd + test = TestCmd.TestCmd() + +There are a bunch of keyword arguments available at instantiation: + + test = TestCmd.TestCmd(description = 'string', + program = 'program_or_script_to_test', + interpreter = 'script_interpreter', + workdir = 'prefix', + subdir = 'subdir', + verbose = Boolean, + match = default_match_function, + match_stdout = default_match_stdout_function, + match_stderr = default_match_stderr_function, + diff = default_diff_stderr_function, + diff_stdout = default_diff_stdout_function, + diff_stderr = default_diff_stderr_function, + combine = Boolean) + +There are a bunch of methods that let you do different things: + + test.verbose_set(1) + + test.description_set('string') + + test.program_set('program_or_script_to_test') + + test.interpreter_set('script_interpreter') + test.interpreter_set(['script_interpreter', 'arg']) + + test.workdir_set('prefix') + test.workdir_set('') + + test.workpath('file') + test.workpath('subdir', 'file') + + test.subdir('subdir', ...) + + test.rmdir('subdir', ...) + + test.write('file', "contents\n") + test.write(['subdir', 'file'], "contents\n") + + test.read('file') + test.read(['subdir', 'file']) + test.read('file', mode) + test.read(['subdir', 'file'], mode) + + test.writable('dir', 1) + test.writable('dir', None) + + test.preserve(condition, ...) + + test.cleanup(condition) + + test.command_args(program = 'program_or_script_to_run', + interpreter = 'script_interpreter', + arguments = 'arguments to pass to program') + + test.run(program = 'program_or_script_to_run', + interpreter = 'script_interpreter', + arguments = 'arguments to pass to program', + chdir = 'directory_to_chdir_to', + stdin = 'input to feed to the program\n') + universal_newlines = True) + + p = test.start(program = 'program_or_script_to_run', + interpreter = 'script_interpreter', + arguments = 'arguments to pass to program', + universal_newlines = None) + + test.finish(self, p) + + test.pass_test() + test.pass_test(condition) + test.pass_test(condition, function) + + test.fail_test() + test.fail_test(condition) + test.fail_test(condition, function) + test.fail_test(condition, function, skip) + test.fail_test(condition, function, skip, message) + + test.no_result() + test.no_result(condition) + test.no_result(condition, function) + test.no_result(condition, function, skip) + + test.stdout() + test.stdout(run) + + test.stderr() + test.stderr(run) + + test.symlink(target, link) + + test.banner(string) + test.banner(string, width) + + test.diff(actual, expected) + + test.diff_stderr(actual, expected) + + test.diff_stdout(actual, expected) + + test.match(actual, expected) + + test.match_stderr(actual, expected) + + test.match_stdout(actual, expected) + + test.set_match_function(match, stdout, stderr) + + test.match_exact("actual 1\nactual 2\n", "expected 1\nexpected 2\n") + test.match_exact(["actual 1\n", "actual 2\n"], + ["expected 1\n", "expected 2\n"]) + test.match_caseinsensitive("Actual 1\nACTUAL 2\n", "expected 1\nEXPECTED 2\n") + + test.match_re("actual 1\nactual 2\n", regex_string) + test.match_re(["actual 1\n", "actual 2\n"], list_of_regexes) + + test.match_re_dotall("actual 1\nactual 2\n", regex_string) + test.match_re_dotall(["actual 1\n", "actual 2\n"], list_of_regexes) + + test.tempdir() + test.tempdir('temporary-directory') + + test.sleep() + test.sleep(seconds) + + test.where_is('foo') + test.where_is('foo', 'PATH1:PATH2') + test.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4') + + test.unlink('file') + test.unlink('subdir', 'file') + +The TestCmd module provides pass_test(), fail_test(), and no_result() +unbound functions that report test results for use with the Aegis change +management system. These methods terminate the test immediately, +reporting PASSED, FAILED, or NO RESULT respectively, and exiting with +status 0 (success), 1 or 2 respectively. This allows for a distinction +between an actual failed test and a test that could not be properly +evaluated because of an external condition (such as a full file system +or incorrect permissions). + + import TestCmd + + TestCmd.pass_test() + TestCmd.pass_test(condition) + TestCmd.pass_test(condition, function) + + TestCmd.fail_test() + TestCmd.fail_test(condition) + TestCmd.fail_test(condition, function) + TestCmd.fail_test(condition, function, skip) + TestCmd.fail_test(condition, function, skip, message) + + TestCmd.no_result() + TestCmd.no_result(condition) + TestCmd.no_result(condition, function) + TestCmd.no_result(condition, function, skip) + +The TestCmd module also provides unbound global functions that handle +matching in the same way as the match_*() methods described above. + + import TestCmd + + test = TestCmd.TestCmd(match = TestCmd.match_exact) + + test = TestCmd.TestCmd(match = TestCmd.match_caseinsensitive) + + test = TestCmd.TestCmd(match = TestCmd.match_re) + + test = TestCmd.TestCmd(match = TestCmd.match_re_dotall) + +These functions are also available as static methods: + + import TestCmd + + test = TestCmd.TestCmd(match = TestCmd.TestCmd.match_exact) + + test = TestCmd.TestCmd(match = TestCmd.TestCmd.match_caseinsensitive) + + test = TestCmd.TestCmd(match = TestCmd.TestCmd.match_re) + + test = TestCmd.TestCmd(match = TestCmd.TestCmd.match_re_dotall) + +These static methods can be accessed by a string naming the method: + + import TestCmd + + test = TestCmd.TestCmd(match = 'match_exact') + + test = TestCmd.TestCmd(match = 'match_caseinsensitive') + + test = TestCmd.TestCmd(match = 'match_re') + + test = TestCmd.TestCmd(match = 'match_re_dotall') + +The TestCmd module provides unbound global functions that can be used +for the "diff" argument to TestCmd.TestCmd instantiation: + + import TestCmd + + test = TestCmd.TestCmd(match = TestCmd.match_re, + diff = TestCmd.diff_re) + + test = TestCmd.TestCmd(diff = TestCmd.simple_diff) + + test = TestCmd.TestCmd(diff = TestCmd.context_diff) + + test = TestCmd.TestCmd(diff = TestCmd.unified_diff) + +These functions are also available as static methods: + + import TestCmd + + test = TestCmd.TestCmd(match = TestCmd.TestCmd.match_re, + diff = TestCmd.TestCmd.diff_re) + + test = TestCmd.TestCmd(diff = TestCmd.TestCmd.simple_diff) + + test = TestCmd.TestCmd(diff = TestCmd.TestCmd.context_diff) + + test = TestCmd.TestCmd(diff = TestCmd.TestCmd.unified_diff) + +These static methods can be accessed by a string naming the method: + + import TestCmd + + test = TestCmd.TestCmd(match = 'match_re', diff = 'diff_re') + + test = TestCmd.TestCmd(diff = 'simple_diff') + + test = TestCmd.TestCmd(diff = 'context_diff') + + test = TestCmd.TestCmd(diff = 'unified_diff') + +The "diff" argument can also be used with standard difflib functions: + + import difflib + + test = TestCmd.TestCmd(diff = difflib.context_diff) + + test = TestCmd.TestCmd(diff = difflib.unified_diff) + +Lastly, the where_is() method also exists in an unbound function +version. + + import TestCmd + + TestCmd.where_is('foo') + TestCmd.where_is('foo', 'PATH1:PATH2') + TestCmd.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4') +""" + +# Copyright 2000-2010 Steven Knight +# This module is free software, and you may redistribute it and/or modify +# it under the same terms as Python itself, so long as this copyright message +# and disclaimer are retained in their original form. +# +# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +# THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +# +# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +# AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +from __future__ import division, print_function + +__author__ = "Steven Knight " +__revision__ = "TestCmd.py 1.3.D001 2010/06/03 12:58:27 knight" +__version__ = "1.3" + +import atexit +import difflib +import errno +import os +import re +import shutil +import signal +import stat +import sys +import tempfile +import threading +import time +import traceback +import types + + +IS_PY3 = sys.version_info[0] == 3 +IS_WINDOWS = sys.platform == 'win32' + + +class null(object): + pass + + +_Null = null() + +try: + from collections import UserList, UserString +except ImportError: + # no 'collections' module or no UserFoo in collections + exec('from UserList import UserList') + exec('from UserString import UserString') + +__all__ = [ + 'diff_re', + 'fail_test', + 'no_result', + 'pass_test', + 'match_exact', + 'match_caseinsensitive', + 'match_re', + 'match_re_dotall', + 'python', + '_python_', + 'TestCmd', + 'to_bytes', + 'to_str', +] + + +def is_List(e): + return isinstance(e, (list, UserList)) + + +def to_bytes(s): + if isinstance(s, bytes) or bytes is str: + return s + return bytes(s, 'utf-8') + + +def to_str(s): + if bytes is str or is_String(s): + return s + return str(s, 'utf-8') + + +try: + eval('unicode') +except NameError: + def is_String(e): + return isinstance(e, (str, UserString)) +else: + def is_String(e): + return isinstance(e, (str, unicode, UserString)) + +tempfile.template = 'testcmd.' +if os.name in ('posix', 'nt'): + tempfile.template = 'testcmd.' + str(os.getpid()) + '.' +else: + tempfile.template = 'testcmd.' + +re_space = re.compile('\s') + + +def _caller(tblist, skip): + string = "" + arr = [] + for file, line, name, text in tblist: + if file[-10:] == "TestCmd.py": + break + arr = [(file, line, name, text)] + arr + atfrom = "at" + for file, line, name, text in arr[skip:]: + if name in ("?", ""): + name = "" + else: + name = " (" + name + ")" + string = string + ("%s line %d of %s%s\n" % (atfrom, line, file, name)) + atfrom = "\tfrom" + return string + + +def fail_test(self=None, condition=1, function=None, skip=0, message=None): + """Cause the test to fail. + + By default, the fail_test() method reports that the test FAILED + and exits with a status of 1. If a condition argument is supplied, + the test fails only if the condition is true. + """ + if not condition: + return + if not function is None: + function() + of = "" + desc = "" + sep = " " + if not self is None: + if self.program: + of = " of " + self.program + sep = "\n\t" + if self.description: + desc = " [" + self.description + "]" + sep = "\n\t" + + at = _caller(traceback.extract_stack(), skip) + if message: + msg = "\t%s\n" % message + else: + msg = "" + sys.stderr.write("FAILED test" + of + desc + sep + at + msg) + + sys.exit(1) + + +def no_result(self=None, condition=1, function=None, skip=0): + """Causes a test to exit with no valid result. + + By default, the no_result() method reports NO RESULT for the test + and exits with a status of 2. If a condition argument is supplied, + the test fails only if the condition is true. + """ + if not condition: + return + if not function is None: + function() + of = "" + desc = "" + sep = " " + if not self is None: + if self.program: + of = " of " + self.program + sep = "\n\t" + if self.description: + desc = " [" + self.description + "]" + sep = "\n\t" + + at = _caller(traceback.extract_stack(), skip) + sys.stderr.write("NO RESULT for test" + of + desc + sep + at) + + sys.exit(2) + + +def pass_test(self=None, condition=1, function=None): + """Causes a test to pass. + + By default, the pass_test() method reports PASSED for the test + and exits with a status of 0. If a condition argument is supplied, + the test passes only if the condition is true. + """ + if not condition: + return + if not function is None: + function() + sys.stderr.write("PASSED\n") + sys.exit(0) + + +def match_exact(lines=None, matches=None, newline=os.sep): + """ + """ + + if isinstance(lines, bytes) or bytes is str: + newline = to_bytes(newline) + + if not is_List(lines): + lines = lines.split(newline) + if not is_List(matches): + matches = matches.split(newline) + if len(lines) != len(matches): + return + for i in range(len(lines)): + if lines[i] != matches[i]: + return + return 1 + + +def match_caseinsensitive(lines=None, matches=None): + """ + """ + if not is_List(lines): + lines = lines.split("\n") + if not is_List(matches): + matches = matches.split("\n") + if len(lines) != len(matches): + return + for i in range(len(lines)): + if lines[i].lower() != matches[i].lower(): + return + return 1 + + +def match_re(lines=None, res=None): + """ + """ + if not is_List(lines): + # CRs mess up matching (Windows) so split carefully + lines = re.split('\r?\n', lines) + if not is_List(res): + res = res.split("\n") + if len(lines) != len(res): + print("match_re: expected %d lines, found %d" % (len(res), len(lines))) + return + for i in range(len(lines)): + s = "^" + res[i] + "$" + try: + expr = re.compile(s) + except re.error as e: + msg = "Regular expression error in %s: %s" + raise re.error(msg % (repr(s), e.args[0])) + if not expr.search(lines[i]): + print("match_re: mismatch at line %d:\n search re='%s'\n line='%s'" % ( + i, s, lines[i])) + return + return 1 + + +def match_re_dotall(lines=None, res=None): + """ + """ + if not isinstance(lines, str): + lines = "\n".join(lines) + if not isinstance(res, str): + res = "\n".join(res) + s = "^" + res + "$" + try: + expr = re.compile(s, re.DOTALL) + except re.error as e: + msg = "Regular expression error in %s: %s" + raise re.error(msg % (repr(s), e.args[0])) + return expr.match(lines) + + +def simple_diff(a, b, fromfile='', tofile='', + fromfiledate='', tofiledate='', n=3, lineterm='\n'): + """ + A function with the same calling signature as difflib.context_diff + (diff -c) and difflib.unified_diff (diff -u) but which prints + output like the simple, unadorned 'diff" command. + """ + a = [to_str(q) for q in a] + b = [to_str(q) for q in b] + sm = difflib.SequenceMatcher(None, a, b) + + def comma(x1, x2): + return x1 + 1 == x2 and str(x2) or '%s,%s' % (x1 + 1, x2) + result = [] + for op, a1, a2, b1, b2 in sm.get_opcodes(): + if op == 'delete': + result.append("%sd%d" % (comma(a1, a2), b1)) + result.extend(['< ' + l for l in a[a1:a2]]) + elif op == 'insert': + result.append("%da%s" % (a1, comma(b1, b2))) + result.extend(['> ' + l for l in b[b1:b2]]) + elif op == 'replace': + result.append("%sc%s" % (comma(a1, a2), comma(b1, b2))) + result.extend(['< ' + l for l in a[a1:a2]]) + result.append('---') + result.extend(['> ' + l for l in b[b1:b2]]) + return result + + +def diff_re(a, b, fromfile='', tofile='', + fromfiledate='', tofiledate='', n=3, lineterm='\n'): + """ + A simple "diff" of two sets of lines when the expected lines + are regular expressions. This is a really dumb thing that + just compares each line in turn, so it doesn't look for + chunks of matching lines and the like--but at least it lets + you know exactly which line first didn't compare correctl... + """ + result = [] + diff = len(a) - len(b) + if diff < 0: + a = a + [''] * (-diff) + elif diff > 0: + b = b + [''] * diff + i = 0 + for aline, bline in zip(a, b): + s = "^" + aline + "$" + try: + expr = re.compile(s) + except re.error as e: + msg = "Regular expression error in %s: %s" + raise re.error(msg % (repr(s), e.args[0])) + if not expr.search(bline): + result.append("%sc%s" % (i + 1, i + 1)) + result.append('< ' + repr(a[i])) + result.append('---') + result.append('> ' + repr(b[i])) + i = i + 1 + return result + + +if os.name == 'posix': + def escape(arg): + "escape shell special characters" + slash = '\\' + special = '"$' + arg = arg.replace(slash, slash + slash) + for c in special: + arg = arg.replace(c, slash + c) + if re_space.search(arg): + arg = '"' + arg + '"' + return arg +else: + # Windows does not allow special characters in file names + # anyway, so no need for an escape function, we will just quote + # the arg. + def escape(arg): + if re_space.search(arg): + arg = '"' + arg + '"' + return arg + +if os.name == 'java': + python = os.path.join(sys.prefix, 'jython') +else: + python = os.environ.get('python_executable', sys.executable) +_python_ = escape(python) + +if sys.platform == 'win32': + + default_sleep_seconds = 2 + + def where_is(file, path=None, pathext=None): + if path is None: + path = os.environ['PATH'] + if is_String(path): + path = path.split(os.pathsep) + if pathext is None: + pathext = os.environ['PATHEXT'] + if is_String(pathext): + pathext = pathext.split(os.pathsep) + for ext in pathext: + if ext.lower() == file[-len(ext):].lower(): + pathext = [''] + break + for dir in path: + f = os.path.join(dir, file) + for ext in pathext: + fext = f + ext + if os.path.isfile(fext): + return fext + return None + +else: + + def where_is(file, path=None, pathext=None): + if path is None: + path = os.environ['PATH'] + if is_String(path): + path = path.split(os.pathsep) + for dir in path: + f = os.path.join(dir, file) + if os.path.isfile(f): + try: + st = os.stat(f) + except OSError: + continue + if stat.S_IMODE(st[stat.ST_MODE]) & 0o111: + return f + return None + + default_sleep_seconds = 1 + + +import subprocess + +try: + subprocess.Popen.terminate +except AttributeError: + if sys.platform == 'win32': + import win32process + + def terminate(self): + win32process.TerminateProcess(self._handle, 1) + else: + def terminate(self): + os.kill(self.pid, signal.SIGTERM) + method = types.MethodType(terminate, None, subprocess.Popen) + setattr(subprocess.Popen, 'terminate', method) + + +# From Josiah Carlson, +# ASPN : Python Cookbook : Module to allow Asynchronous subprocess use on Windows and Posix platforms +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554 + +PIPE = subprocess.PIPE + +if sys.platform == 'win32': # and subprocess.mswindows: + try: + from win32file import ReadFile, WriteFile + from win32pipe import PeekNamedPipe + except ImportError: + # If PyWin32 is not available, try ctypes instead + # XXX These replicate _just_enough_ PyWin32 behaviour for our purposes + import ctypes + from ctypes.wintypes import DWORD + + def ReadFile(hFile, bufSize, ol=None): + assert ol is None + lpBuffer = ctypes.create_string_buffer(bufSize) + bytesRead = DWORD() + bErr = ctypes.windll.kernel32.ReadFile( + hFile, lpBuffer, bufSize, ctypes.byref(bytesRead), ol) + if not bErr: + raise ctypes.WinError() + return (0, ctypes.string_at(lpBuffer, bytesRead.value)) + + def WriteFile(hFile, data, ol=None): + assert ol is None + bytesWritten = DWORD() + bErr = ctypes.windll.kernel32.WriteFile( + hFile, data, len(data), ctypes.byref(bytesWritten), ol) + if not bErr: + raise ctypes.WinError() + return (0, bytesWritten.value) + + def PeekNamedPipe(hPipe, size): + assert size == 0 + bytesAvail = DWORD() + bErr = ctypes.windll.kernel32.PeekNamedPipe( + hPipe, None, size, None, ctypes.byref(bytesAvail), None) + if not bErr: + raise ctypes.WinError() + return ("", bytesAvail.value, None) + import msvcrt +else: + import select + import fcntl + + try: + fcntl.F_GETFL + except AttributeError: + fcntl.F_GETFL = 3 + + try: + fcntl.F_SETFL + except AttributeError: + fcntl.F_SETFL = 4 + + +class Popen(subprocess.Popen): + def recv(self, maxsize=None): + return self._recv('stdout', maxsize) + + def recv_err(self, maxsize=None): + return self._recv('stderr', maxsize) + + def send_recv(self, input='', maxsize=None): + return self.send(input), self.recv(maxsize), self.recv_err(maxsize) + + def get_conn_maxsize(self, which, maxsize): + if maxsize is None: + maxsize = 1024 + elif maxsize < 1: + maxsize = 1 + return getattr(self, which), maxsize + + def _close(self, which): + getattr(self, which).close() + setattr(self, which, None) + + if sys.platform == 'win32': # and subprocess.mswindows: + def send(self, input): + input = to_bytes(input) + if not self.stdin: + return None + + try: + x = msvcrt.get_osfhandle(self.stdin.fileno()) + (errCode, written) = WriteFile(x, input) + except ValueError: + return self._close('stdin') + except (subprocess.pywintypes.error, Exception) as why: + if why.args[0] in (109, errno.ESHUTDOWN): + return self._close('stdin') + raise + + return written + + def _recv(self, which, maxsize): + conn, maxsize = self.get_conn_maxsize(which, maxsize) + if conn is None: + return None + + try: + x = msvcrt.get_osfhandle(conn.fileno()) + (read, nAvail, nMessage) = PeekNamedPipe(x, 0) + if maxsize < nAvail: + nAvail = maxsize + if nAvail > 0: + (errCode, read) = ReadFile(x, nAvail, None) + except ValueError: + return self._close(which) + except (subprocess.pywintypes.error, Exception) as why: + if why.args[0] in (109, errno.ESHUTDOWN): + return self._close(which) + raise + + # if self.universal_newlines: + # read = self._translate_newlines(read) + return read + + else: + def send(self, input): + if not self.stdin: + return None + + if not select.select([], [self.stdin], [], 0)[1]: + return 0 + + try: + written = os.write(self.stdin.fileno(), + bytearray(input, 'utf-8')) + except OSError as why: + if why.args[0] == errno.EPIPE: # broken pipe + return self._close('stdin') + raise + + return written + + def _recv(self, which, maxsize): + conn, maxsize = self.get_conn_maxsize(which, maxsize) + if conn is None: + return None + + try: + flags = fcntl.fcntl(conn, fcntl.F_GETFL) + except TypeError: + flags = None + else: + if not conn.closed: + fcntl.fcntl(conn, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + try: + if not select.select([conn], [], [], 0)[0]: + return '' + + r = conn.read(maxsize) + if not r: + return self._close(which) + + # if self.universal_newlines: + # r = self._translate_newlines(r) + return r + finally: + if not conn.closed and not flags is None: + fcntl.fcntl(conn, fcntl.F_SETFL, flags) + + +disconnect_message = "Other end disconnected!" + + +def recv_some(p, t=.1, e=1, tr=5, stderr=0): + if tr < 1: + tr = 1 + x = time.time() + t + y = [] + r = '' + pr = p.recv + if stderr: + pr = p.recv_err + while time.time() < x or r: + r = pr() + if r is None: + if e: + raise Exception(disconnect_message) + else: + break + elif r: + y.append(r) + else: + time.sleep(max((x - time.time()) / tr, 0)) + return ''.join(y) + + +def send_all(p, data): + while len(data): + sent = p.send(data) + if sent is None: + raise Exception(disconnect_message) + data = memoryview(data)[sent:] + + +_Cleanup = [] + + +def _clean(): + global _Cleanup + cleanlist = [c for c in _Cleanup if c] + del _Cleanup[:] + cleanlist.reverse() + for test in cleanlist: + test.cleanup() + + +atexit.register(_clean) + + +class TestCmd(object): + """Class TestCmd + """ + + def __init__(self, description=None, + program=None, + interpreter=None, + workdir=None, + subdir=None, + verbose=None, + match=None, + match_stdout=None, + match_stderr=None, + diff=None, + diff_stdout=None, + diff_stderr=None, + combine=0, + universal_newlines=True, + timeout=None): + self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) + self._cwd = os.getcwd() + self.description_set(description) + self.program_set(program) + self.interpreter_set(interpreter) + if verbose is None: + try: + verbose = max(0, int(os.environ.get('TESTCMD_VERBOSE', 0))) + except ValueError: + verbose = 0 + self.verbose_set(verbose) + self.combine = combine + self.universal_newlines = universal_newlines + self.process = None + self.set_timeout(timeout) + self.set_match_function(match, match_stdout, match_stderr) + self.set_diff_function(diff, diff_stdout, diff_stderr) + self._dirlist = [] + self._preserve = {'pass_test': 0, 'fail_test': 0, 'no_result': 0} + preserve_value = os.environ.get('PRESERVE', False) + if preserve_value not in [0, '0', 'False']: + self._preserve['pass_test'] = os.environ['PRESERVE'] + self._preserve['fail_test'] = os.environ['PRESERVE'] + self._preserve['no_result'] = os.environ['PRESERVE'] + else: + try: + self._preserve['pass_test'] = os.environ['PRESERVE_PASS'] + except KeyError: + pass + try: + self._preserve['fail_test'] = os.environ['PRESERVE_FAIL'] + except KeyError: + pass + try: + self._preserve['no_result'] = os.environ['PRESERVE_NO_RESULT'] + except KeyError: + pass + self._stdout = [] + self._stderr = [] + self.status = None + self.condition = 'no_result' + self.workdir_set(workdir) + self.subdir(subdir) + self.fixture_dirs = [] + + def __del__(self): + self.cleanup() + + def __repr__(self): + return "%x" % id(self) + + banner_char = '=' + banner_width = 80 + + def banner(self, s, width=None): + if width is None: + width = self.banner_width + return s + self.banner_char * (width - len(s)) + + escape = staticmethod(escape) + + def canonicalize(self, path): + if is_List(path): + path = os.path.join(*tuple(path)) + if not os.path.isabs(path): + path = os.path.join(self.workdir, path) + return path + + def chmod(self, path, mode): + """Changes permissions on the specified file or directory + path name.""" + path = self.canonicalize(path) + os.chmod(path, mode) + + def cleanup(self, condition=None): + """Removes any temporary working directories for the specified + TestCmd environment. If the environment variable PRESERVE was + set when the TestCmd environment was created, temporary working + directories are not removed. If any of the environment variables + PRESERVE_PASS, PRESERVE_FAIL, or PRESERVE_NO_RESULT were set + when the TestCmd environment was created, then temporary working + directories are not removed if the test passed, failed, or had + no result, respectively. Temporary working directories are also + preserved for conditions specified via the preserve method. + + Typically, this method is not called directly, but is used when + the script exits to clean up temporary working directories as + appropriate for the exit status. + """ + if not self._dirlist: + return + os.chdir(self._cwd) + self.workdir = None + if condition is None: + condition = self.condition + if self._preserve[condition]: + for dir in self._dirlist: + print(u"Preserved directory " + dir + "\n") + else: + list = self._dirlist[:] + list.reverse() + for dir in list: + self.writable(dir, 1) + shutil.rmtree(dir, ignore_errors=1) + self._dirlist = [] + + global _Cleanup + if self in _Cleanup: + _Cleanup.remove(self) + + def command_args(self, program=None, + interpreter=None, + arguments=None): + if not self.external: + if program: + if isinstance(program, str) and not os.path.isabs(program): + program = os.path.join(self._cwd, program) + else: + program = self.program + if not interpreter: + interpreter = self.interpreter + else: + if not program: + program = self.program + if not interpreter: + interpreter = self.interpreter + if not isinstance(program, (list, tuple)): + program = [program] + cmd = list(program) + if interpreter: + if not isinstance(interpreter, (list, tuple)): + interpreter = [interpreter] + cmd = list(interpreter) + cmd + if arguments: + if isinstance(arguments, str): + arguments = arguments.split() + cmd.extend(arguments) + return cmd + + def description_set(self, description): + """Set the description of the functionality being tested. + """ + self.description = description + + def set_diff_function(self, diff=_Null, stdout=_Null, stderr=_Null): + """Sets the specified diff functions. + """ + if diff is not _Null: + self._diff_function = diff + if stdout is not _Null: + self._diff_stdout_function = stdout + if stderr is not _Null: + self._diff_stderr_function = stderr + + def diff(self, a, b, name=None, diff_function=None, *args, **kw): + if diff_function is None: + try: + diff_function = getattr(self, self._diff_function) + except TypeError: + diff_function = self._diff_function + if diff_function is None: + diff_function = self.simple_diff + if name is not None: + print(self.banner(name)) + args = (a.splitlines(), b.splitlines()) + args + for line in diff_function(*args, **kw): + print(line) + + def diff_stderr(self, a, b, *args, **kw): + """Compare actual and expected file contents. + """ + try: + diff_stderr_function = getattr(self, self._diff_stderr_function) + except TypeError: + diff_stderr_function = self._diff_stderr_function + return self.diff(a, b, diff_function=diff_stderr_function, *args, **kw) + + def diff_stdout(self, a, b, *args, **kw): + """Compare actual and expected file contents. + """ + try: + diff_stdout_function = getattr(self, self._diff_stdout_function) + except TypeError: + diff_stdout_function = self._diff_stdout_function + return self.diff(a, b, diff_function=diff_stdout_function, *args, **kw) + + simple_diff = staticmethod(simple_diff) + + diff_re = staticmethod(diff_re) + + context_diff = staticmethod(difflib.context_diff) + + unified_diff = staticmethod(difflib.unified_diff) + + def fail_test(self, condition=1, function=None, skip=0, message=None): + """Cause the test to fail. + """ + if not condition: + return + self.condition = 'fail_test' + fail_test(self=self, + condition=condition, + function=function, + skip=skip, + message=message) + + def interpreter_set(self, interpreter): + """Set the program to be used to interpret the program + under test as a script. + """ + self.interpreter = interpreter + + def set_match_function(self, match=_Null, stdout=_Null, stderr=_Null): + """Sets the specified match functions. + """ + if match is not _Null: + self._match_function = match + if stdout is not _Null: + self._match_stdout_function = stdout + if stderr is not _Null: + self._match_stderr_function = stderr + + def match(self, lines, matches): + """Compare actual and expected file contents. + """ + try: + match_function = getattr(self, self._match_function) + except TypeError: + match_function = self._match_function + if match_function is None: + # Default is regular expression matches. + match_function = self.match_re + return match_function(lines, matches) + + def match_stderr(self, lines, matches): + """Compare actual and expected file contents. + """ + try: + match_stderr_function = getattr(self, self._match_stderr_function) + except TypeError: + match_stderr_function = self._match_stderr_function + if match_stderr_function is None: + # Default is to use whatever match= is set to. + match_stderr_function = self.match + return match_stderr_function(lines, matches) + + def match_stdout(self, lines, matches): + """Compare actual and expected file contents. + """ + try: + match_stdout_function = getattr(self, self._match_stdout_function) + except TypeError: + match_stdout_function = self._match_stdout_function + if match_stdout_function is None: + # Default is to use whatever match= is set to. + match_stdout_function = self.match + return match_stdout_function(lines, matches) + + match_exact = staticmethod(match_exact) + + match_caseinsensitive = staticmethod(match_caseinsensitive) + + match_re = staticmethod(match_re) + + match_re_dotall = staticmethod(match_re_dotall) + + def no_result(self, condition=1, function=None, skip=0): + """Report that the test could not be run. + """ + if not condition: + return + self.condition = 'no_result' + no_result(self=self, + condition=condition, + function=function, + skip=skip) + + def pass_test(self, condition=1, function=None): + """Cause the test to pass. + """ + if not condition: + return + self.condition = 'pass_test' + pass_test(self=self, condition=condition, function=function) + + def preserve(self, *conditions): + """Arrange for the temporary working directories for the + specified TestCmd environment to be preserved for one or more + conditions. If no conditions are specified, arranges for + the temporary working directories to be preserved for all + conditions. + """ + if conditions is (): + conditions = ('pass_test', 'fail_test', 'no_result') + for cond in conditions: + self._preserve[cond] = 1 + + def program_set(self, program): + """Set the executable program or script to be tested. + """ + if not self.external: + if program and not os.path.isabs(program): + program = os.path.join(self._cwd, program) + self.program = program + + def read(self, file, mode='rb', newline=None): + """Reads and returns the contents of the specified file name. + The file name may be a list, in which case the elements are + concatenated with the os.path.join() method. The file is + assumed to be under the temporary working directory unless it + is an absolute path name. The I/O mode for the file may + be specified; it must begin with an 'r'. The default is + 'rb' (binary read). + """ + file = self.canonicalize(file) + if mode[0] != 'r': + raise ValueError("mode must begin with 'r'") + if IS_PY3 and 'b' not in mode: + return open(file, mode, newline=newline).read() + else: + return open(file, mode).read() + + def rmdir(self, dir): + """Removes the specified dir name. + The dir name may be a list, in which case the elements are + concatenated with the os.path.join() method. The dir is + assumed to be under the temporary working directory unless it + is an absolute path name. + The dir must be empty. + """ + dir = self.canonicalize(dir) + os.rmdir(dir) + + def _timeout(self): + self.process.terminate() + self.timer.cancel() + self.timer = None + + def set_timeout(self, timeout): + self.timeout = timeout + self.timer = None + + def parse_path(self, path, suppress_current=False): + """Return a list with the single path components of path. + """ + head, tail = os.path.split(path) + result = [] + if not tail: + if head == path: + return [head] + else: + result.append(tail) + head, tail = os.path.split(head) + while head and tail: + result.append(tail) + head, tail = os.path.split(head) + result.append(head or tail) + result.reverse() + + return result + + def dir_fixture(self, srcdir, dstdir=None): + """Copies the contents of the specified folder srcdir from + the directory of the called script, to the current + working directory. + The srcdir name may be a list, in which case the elements are + concatenated with the os.path.join() method. The dstdir is + assumed to be under the temporary working directory, it gets + created automatically, if it does not already exist. + """ + + if srcdir and self.fixture_dirs and not os.path.isabs(srcdir): + for dir in self.fixture_dirs: + spath = os.path.join(dir, srcdir) + if os.path.isdir(spath): + break + else: + spath = srcdir + + if dstdir: + dstdir = self.canonicalize(dstdir) + else: + dstdir = '.' + + if dstdir != '.' and not os.path.exists(dstdir): + dstlist = self.parse_path(dstdir) + if len(dstlist) > 0 and dstlist[0] == ".": + dstlist = dstlist[1:] + for idx in range(len(dstlist)): + self.subdir(dstlist[:idx + 1]) + + if dstdir and self.workdir: + dstdir = os.path.join(self.workdir, dstdir) + + for entry in os.listdir(spath): + epath = os.path.join(spath, entry) + dpath = os.path.join(dstdir, entry) + if os.path.isdir(epath): + # Copy the subfolder + shutil.copytree(epath, dpath) + else: + shutil.copy(epath, dpath) + + def file_fixture(self, srcfile, dstfile=None): + """Copies the file srcfile from the directory of + the called script, to the current working directory. + The dstfile is assumed to be under the temporary working + directory unless it is an absolute path name. + If dstfile is specified its target directory gets created + automatically, if it does not already exist. + """ + srcpath, srctail = os.path.split(srcfile) + + if srcpath and (not self.fixture_dirs or os.path.isabs(srcpath)): + spath = srcfile + else: + for dir in self.fixture_dirs: + spath = os.path.join(dir, srcfile) + if os.path.isfile(spath): + break + + if not dstfile: + if srctail: + dpath = os.path.join(self.workdir, srctail) + else: + return + else: + dstpath, dsttail = os.path.split(dstfile) + if dstpath: + if not os.path.exists(os.path.join(self.workdir, dstpath)): + dstlist = self.parse_path(dstpath) + if len(dstlist) > 0 and dstlist[0] == ".": + dstlist = dstlist[1:] + for idx in range(len(dstlist)): + self.subdir(dstlist[:idx + 1]) + + dpath = os.path.join(self.workdir, dstfile) + shutil.copy(spath, dpath) + + def start(self, program=None, + interpreter=None, + arguments=None, + universal_newlines=None, + timeout=_Null, + **kw): + """ + Starts a program or script for the test environment. + + The specified program will have the original directory + prepended unless it is enclosed in a [list]. + """ + cmd = self.command_args(program, interpreter, arguments) + if self.verbose: + cmd_string = ' '.join([self.escape(c) for c in cmd]) + sys.stderr.write(cmd_string + "\n") + if universal_newlines is None: + universal_newlines = self.universal_newlines + + # On Windows, if we make stdin a pipe when we plan to send + # no input, and the test program exits before + # Popen calls msvcrt.open_osfhandle, that call will fail. + # So don't use a pipe for stdin if we don't need one. + stdin = kw.get('stdin', None) + if stdin is not None: + stdin = subprocess.PIPE + + combine = kw.get('combine', self.combine) + if combine: + stderr_value = subprocess.STDOUT + else: + stderr_value = subprocess.PIPE + + if timeout is _Null: + timeout = self.timeout + if timeout: + self.timer = threading.Timer(float(timeout), self._timeout) + self.timer.start() + + if IS_PY3 and sys.platform == 'win32': + # Set this otherwist stdout/stderr pipes default to + # windows default locale cp1252 which will throw exception + # if using non-ascii characters. + # For example test/Install/non-ascii-name.py + os.environ['PYTHONIOENCODING'] = 'utf-8' + + # It seems that all pythons up to py3.6 still set text mode if you set encoding. + # TODO: File enhancement request on python to propagate universal_newlines even + # if encoding is set.hg c + p = Popen(cmd, + stdin=stdin, + stdout=subprocess.PIPE, + stderr=stderr_value, + env=os.environ, + universal_newlines=False) + + self.process = p + return p + + @staticmethod + def fix_binary_stream(stream): + """ + Handle stdout/stderr from popen when we specify universal_newlines = False. + This will read from the pipes in binary mode, not decode the output, + and not convert line endings to \n. + We do this because in py3 (3.5) with universal_newlines=True, it will + choose the default system locale to decode the output, and this breaks unicode + output. Specifically breaking test/option--tree.py which outputs a unicode char. + + py 3.6 allows us to pass an encoding param to popen thus not requiring the decode + nor end of line handling, because we propagate universal_newlines as specified. + + TODO: Do we need to pass universal newlines into this function? + """ + + if not stream: + return stream + # TODO: Run full tests on both platforms and see if this fixes failures + # It seems that py3.6 still sets text mode if you set encoding. + elif sys.version_info[0] == 3:# TODO and sys.version_info[1] < 6: + stream = stream.decode('utf-8') + stream = stream.replace('\r\n', '\n') + elif sys.version_info[0] == 2: + stream = stream.replace('\r\n', '\n') + + return stream + + + def finish(self, popen=None, **kw): + """ + Finishes and waits for the process being run under control of + the specified popen argument, recording the exit status, + standard output and error output. + """ + if popen is None: + popen = self.process + stdout, stderr = popen.communicate() + + stdout = self.fix_binary_stream(stdout) + stderr = self.fix_binary_stream(stderr) + + if self.timer: + self.timer.cancel() + self.timer = None + self.status = popen.returncode + self.process = None + self._stdout.append(stdout or '') + self._stderr.append(stderr or '') + + def run(self, program=None, + interpreter=None, + arguments=None, + chdir=None, + stdin=None, + universal_newlines=None, + timeout=_Null): + """Runs a test of the program or script for the test + environment. Standard output and error output are saved for + future retrieval via the stdout() and stderr() methods. + + The specified program will have the original directory + prepended unless it is enclosed in a [list]. + """ + if self.external: + if not program: + program = self.program + if not interpreter: + interpreter = self.interpreter + + if universal_newlines is None: + universal_newlines = self.universal_newlines + + if chdir: + oldcwd = os.getcwd() + if not os.path.isabs(chdir): + chdir = os.path.join(self.workpath(chdir)) + if self.verbose: + sys.stderr.write("chdir(" + chdir + ")\n") + os.chdir(chdir) + p = self.start(program=program, + interpreter=interpreter, + arguments=arguments, + universal_newlines=universal_newlines, + timeout=timeout, + stdin=stdin) + if is_List(stdin): + stdin = ''.join(stdin) + + if stdin and IS_PY3:# and sys.version_info[1] < 6: + stdin = to_bytes(stdin) + + # TODO(sgk): figure out how to re-use the logic in the .finish() + # method above. Just calling it from here causes problems with + # subclasses that redefine .finish(). We could abstract this + # into Yet Another common method called both here and by .finish(), + # but that seems ill-thought-out. + stdout, stderr = p.communicate(input=stdin) + if self.timer: + self.timer.cancel() + self.timer = None + self.status = p.returncode + self.process = None + + stdout = self.fix_binary_stream(stdout) + stderr = self.fix_binary_stream(stderr) + + + self._stdout.append(stdout or '') + self._stderr.append(stderr or '') + + if chdir: + os.chdir(oldcwd) + if self.verbose >= 2: + write = sys.stdout.write + write('============ STATUS: %d\n' % self.status) + out = self.stdout() + if out or self.verbose >= 3: + write('============ BEGIN STDOUT (len=%d):\n' % len(out)) + write(out) + write('============ END STDOUT\n') + err = self.stderr() + if err or self.verbose >= 3: + write('============ BEGIN STDERR (len=%d)\n' % len(err)) + write(err) + write('============ END STDERR\n') + + def sleep(self, seconds=default_sleep_seconds): + """Sleeps at least the specified number of seconds. If no + number is specified, sleeps at least the minimum number of + seconds necessary to advance file time stamps on the current + system. Sleeping more seconds is all right. + """ + time.sleep(seconds) + + def stderr(self, run=None): + """Returns the error output from the specified run number. + If there is no specified run number, then returns the error + output of the last run. If the run number is less than zero, + then returns the error output from that many runs back from the + current run. + """ + if not run: + run = len(self._stderr) + elif run < 0: + run = len(self._stderr) + run + run = run - 1 + return self._stderr[run] + + def stdout(self, run=None): + """Returns the standard output from the specified run number. + If there is no specified run number, then returns the standard + output of the last run. If the run number is less than zero, + then returns the standard output from that many runs back from + the current run. + """ + if not run: + run = len(self._stdout) + elif run < 0: + run = len(self._stdout) + run + run = run - 1 + return self._stdout[run] + + def subdir(self, *subdirs): + """Create new subdirectories under the temporary working + directory, one for each argument. An argument may be a list, + in which case the list elements are concatenated using the + os.path.join() method. Subdirectories multiple levels deep + must be created using a separate argument for each level: + + test.subdir('sub', ['sub', 'dir'], ['sub', 'dir', 'ectory']) + + Returns the number of subdirectories actually created. + """ + count = 0 + for sub in subdirs: + if sub is None: + continue + if is_List(sub): + sub = os.path.join(*tuple(sub)) + new = os.path.join(self.workdir, sub) + try: + os.mkdir(new) + except OSError: + pass + else: + count = count + 1 + return count + + def symlink(self, target, link): + """Creates a symlink to the specified target. + The link name may be a list, in which case the elements are + concatenated with the os.path.join() method. The link is + assumed to be under the temporary working directory unless it + is an absolute path name. The target is *not* assumed to be + under the temporary working directory. + """ + if sys.platform == 'win32': + # Skip this on windows as we're not enabling it due to + # it requiring user permissions which aren't always present + # and we don't have a good way to detect those permissions yet. + return + link = self.canonicalize(link) + try: + os.symlink(target, link) + except AttributeError: + pass # Windows has no symlink + + def tempdir(self, path=None): + """Creates a temporary directory. + A unique directory name is generated if no path name is specified. + The directory is created, and will be removed when the TestCmd + object is destroyed. + """ + if path is None: + try: + path = tempfile.mktemp(prefix=tempfile.template) + except TypeError: + path = tempfile.mktemp() + os.mkdir(path) + + # Symlinks in the path will report things + # differently from os.getcwd(), so chdir there + # and back to fetch the canonical path. + cwd = os.getcwd() + try: + os.chdir(path) + path = os.getcwd() + finally: + os.chdir(cwd) + + # Uppercase the drive letter since the case of drive + # letters is pretty much random on win32: + drive, rest = os.path.splitdrive(path) + if drive: + path = drive.upper() + rest + + # + self._dirlist.append(path) + + global _Cleanup + if self not in _Cleanup: + _Cleanup.append(self) + + return path + + def touch(self, path, mtime=None): + """Updates the modification time on the specified file or + directory path name. The default is to update to the + current time if no explicit modification time is specified. + """ + path = self.canonicalize(path) + atime = os.path.getatime(path) + if mtime is None: + mtime = time.time() + os.utime(path, (atime, mtime)) + + def unlink(self, file): + """Unlinks the specified file name. + The file name may be a list, in which case the elements are + concatenated with the os.path.join() method. The file is + assumed to be under the temporary working directory unless it + is an absolute path name. + """ + file = self.canonicalize(file) + os.unlink(file) + + def verbose_set(self, verbose): + """Set the verbose level. + """ + self.verbose = verbose + + def where_is(self, file, path=None, pathext=None): + """Find an executable file. + """ + if is_List(file): + file = os.path.join(*tuple(file)) + if not os.path.isabs(file): + file = where_is(file, path, pathext) + return file + + def workdir_set(self, path): + """Creates a temporary working directory with the specified + path name. If the path is a null string (''), a unique + directory name is created. + """ + if (path != None): + if path == '': + path = None + path = self.tempdir(path) + self.workdir = path + + def workpath(self, *args): + """Returns the absolute path name to a subdirectory or file + within the current temporary working directory. Concatenates + the temporary working directory name with the specified + arguments using the os.path.join() method. + """ + return os.path.join(self.workdir, *tuple(args)) + + def readable(self, top, read=1): + """Make the specified directory tree readable (read == 1) + or not (read == None). + + This method has no effect on Windows systems, which use a + completely different mechanism to control file readability. + """ + + if sys.platform == 'win32': + return + + if read: + def do_chmod(fname): + try: + st = os.stat(fname) + except OSError: + pass + else: + os.chmod(fname, stat.S_IMODE( + st[stat.ST_MODE] | stat.S_IREAD)) + else: + def do_chmod(fname): + try: + st = os.stat(fname) + except OSError: + pass + else: + os.chmod(fname, stat.S_IMODE( + st[stat.ST_MODE] & ~stat.S_IREAD)) + + if os.path.isfile(top): + # If it's a file, that's easy, just chmod it. + do_chmod(top) + elif read: + # It's a directory and we're trying to turn on read + # permission, so it's also pretty easy, just chmod the + # directory and then chmod every entry on our walk down the + # tree. + do_chmod(top) + for dirpath, dirnames, filenames in os.walk(top): + for name in dirnames + filenames: + do_chmod(os.path.join(dirpath, name)) + else: + # It's a directory and we're trying to turn off read + # permission, which means we have to chmod the directories + # in the tree bottom-up, lest disabling read permission from + # the top down get in the way of being able to get at lower + # parts of the tree. + for dirpath, dirnames, filenames in os.walk(top, topdown=0): + for name in dirnames + filenames: + do_chmod(os.path.join(dirpath, name)) + do_chmod(top) + + def writable(self, top, write=1): + """Make the specified directory tree writable (write == 1) + or not (write == None). + """ + + if sys.platform == 'win32': + + if write: + def do_chmod(fname): + try: + os.chmod(fname, stat.S_IWRITE) + except OSError: + pass + else: + def do_chmod(fname): + try: + os.chmod(fname, stat.S_IREAD) + except OSError: + pass + + else: + + if write: + def do_chmod(fname): + try: + st = os.stat(fname) + except OSError: + pass + else: + os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE] | 0o200)) + else: + def do_chmod(fname): + try: + st = os.stat(fname) + except OSError: + pass + else: + os.chmod(fname, stat.S_IMODE( + st[stat.ST_MODE] & ~0o200)) + + if os.path.isfile(top): + do_chmod(top) + else: + do_chmod(top) + for dirpath, dirnames, filenames in os.walk(top, topdown=0): + for name in dirnames + filenames: + do_chmod(os.path.join(dirpath, name)) + + def executable(self, top, execute=1): + """Make the specified directory tree executable (execute == 1) + or not (execute == None). + + This method has no effect on Windows systems, which use a + completely different mechanism to control file executability. + """ + + if sys.platform == 'win32': + return + + if execute: + def do_chmod(fname): + try: + st = os.stat(fname) + except OSError: + pass + else: + os.chmod(fname, stat.S_IMODE( + st[stat.ST_MODE] | stat.S_IEXEC)) + else: + def do_chmod(fname): + try: + st = os.stat(fname) + except OSError: + pass + else: + os.chmod(fname, stat.S_IMODE( + st[stat.ST_MODE] & ~stat.S_IEXEC)) + + if os.path.isfile(top): + # If it's a file, that's easy, just chmod it. + do_chmod(top) + elif execute: + # It's a directory and we're trying to turn on execute + # permission, so it's also pretty easy, just chmod the + # directory and then chmod every entry on our walk down the + # tree. + do_chmod(top) + for dirpath, dirnames, filenames in os.walk(top): + for name in dirnames + filenames: + do_chmod(os.path.join(dirpath, name)) + else: + # It's a directory and we're trying to turn off execute + # permission, which means we have to chmod the directories + # in the tree bottom-up, lest disabling execute permission from + # the top down get in the way of being able to get at lower + # parts of the tree. + for dirpath, dirnames, filenames in os.walk(top, topdown=0): + for name in dirnames + filenames: + do_chmod(os.path.join(dirpath, name)) + do_chmod(top) + + def write(self, file, content, mode='wb'): + """Writes the specified content text (second argument) to the + specified file name (first argument). The file name may be + a list, in which case the elements are concatenated with the + os.path.join() method. The file is created under the temporary + working directory. Any subdirectories in the path must already + exist. The I/O mode for the file may be specified; it must + begin with a 'w'. The default is 'wb' (binary write). + """ + file = self.canonicalize(file) + if mode[0] != 'w': + raise ValueError("mode must begin with 'w'") + with open(file, mode) as f: + try: + f.write(content) + except TypeError as e: + # python 3 default strings are not bytes, but unicode + f.write(bytes(content, 'utf-8')) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/testing/framework/TestCmdTests.py b/testing/framework/TestCmdTests.py new file mode 100644 index 0000000..b9226fd --- /dev/null +++ b/testing/framework/TestCmdTests.py @@ -0,0 +1,3419 @@ +#!/usr/bin/env python +""" +TestCmdTests.py: Unit tests for the TestCmd.py module. + +Copyright 2000-2010 Steven Knight +This module is free software, and you may redistribute it and/or modify +it under the same terms as Python itself, so long as this copyright message +and disclaimer are retained in their original form. + +IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +""" + +__author__ = "Steven Knight " +__revision__ = "TestCmdTests.py 1.3.D001 2010/06/03 12:58:27 knight" + +import os +import shutil +import signal +import stat +from StringIO import StringIO +import sys +import tempfile +import time +import types +import unittest +from UserList import UserList + + +# Strip the current directory so we get the right TestCmd.py module. +sys.path = sys.path[1:] + +import TestCmd + +def _is_readable(path): + # XXX this doesn't take into account UID, it assumes it's our file + return os.stat(path)[stat.ST_MODE] & stat.S_IREAD + +def _is_writable(path): + # XXX this doesn't take into account UID, it assumes it's our file + return os.stat(path)[stat.ST_MODE] & stat.S_IWRITE + +def _is_executable(path): + # XXX this doesn't take into account UID, it assumes it's our file + return os.stat(path)[stat.ST_MODE] & stat.S_IEXEC + +def _clear_dict(dict, *keys): + for key in keys: + try: + dict[key] = '' # del dict[key] + except KeyError: + pass + +import subprocess + +try: + subprocess.Popen.terminate +except AttributeError: + if sys.platform == 'win32': + import win32process + def terminate(self): + win32process.TerminateProcess(self._handle, 1) + else: + def terminate(self): + os.kill(self.pid, signal.SIGTERM) + method = types.MethodType(terminate, None, subprocess.Popen) + setattr(subprocess.Popen, 'terminate', method) + +class ExitError(Exception): + pass + +class TestCmdTestCase(unittest.TestCase): + """Base class for TestCmd test cases, with fixture and utility methods.""" + + def setUp(self): + self.orig_cwd = os.getcwd() + + def tearDown(self): + os.chdir(self.orig_cwd) + + def setup_run_scripts(self): + class T: + pass + + t = T() + + t.script = 'script' + t.scriptx = 'scriptx.bat' + t.script1 = 'script_1.txt' + t.scriptout = 'scriptout' + t.scripterr = 'scripterr' + fmt = "import os, sys; cwd = os.getcwd(); " + \ + "sys.stdout.write('%s: STDOUT: %%s: %%s\\n' %% (cwd, sys.argv[1:])); " + \ + "sys.stderr.write('%s: STDERR: %%s: %%s\\n' %% (cwd, sys.argv[1:]))" + fmtout = "import os, sys; cwd = os.getcwd(); " + \ + "sys.stdout.write('%s: STDOUT: %%s: %%s\\n' %% (cwd, sys.argv[1:]))" + fmterr = "import os, sys; cwd = os.getcwd(); " + \ + "sys.stderr.write('%s: STDERR: %%s: %%s\\n' %% (cwd, sys.argv[1:]))" + text = fmt % (t.script, t.script) + textx = fmt % (t.scriptx, t.scriptx) + if sys.platform == 'win32': + textx = textx.replace('%', '%%') + textx = '@python -c "%s"' % textx + ' %1 %2 %3 %4 %5 %6 %7 %8 %9\n' + else: + textx = '#! /usr/bin/env python\n' + textx + '\n' + text1 = 'A first line to be ignored!\n' + fmt % (t.script1, t.script1) + textout = fmtout % (t.scriptout) + texterr = fmterr % (t.scripterr) + + run_env = TestCmd.TestCmd(workdir = '') + run_env.subdir('sub dir') + t.run_env = run_env + + t.sub_dir = run_env.workpath('sub dir') + t.script_path = run_env.workpath('sub dir', t.script) + t.scriptx_path = run_env.workpath('sub dir', t.scriptx) + t.script1_path = run_env.workpath('sub dir', t.script1) + t.scriptout_path = run_env.workpath('sub dir', t.scriptout) + t.scripterr_path = run_env.workpath('sub dir', t.scripterr) + + run_env.write(t.script_path, text) + run_env.write(t.scriptx_path, textx) + run_env.write(t.script1_path, text1) + run_env.write(t.scriptout_path, textout) + run_env.write(t.scripterr_path, texterr) + + os.chmod(t.script_path, 0o644) # XXX UNIX-specific + os.chmod(t.scriptx_path, 0o755) # XXX UNIX-specific + os.chmod(t.script1_path, 0o644) # XXX UNIX-specific + os.chmod(t.scriptout_path, 0o644) # XXX UNIX-specific + os.chmod(t.scripterr_path, 0o644) # XXX UNIX-specific + + t.orig_cwd = os.getcwd() + + t.workdir = run_env.workpath('sub dir') + os.chdir(t.workdir) + + return t + + def translate_newlines(self, data): + data = data.replace("\r\n", "\n") + return data + + def call_python(self, input, python=None): + if python is None: + python = sys.executable + p = subprocess.Popen(python, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + stdout, stderr = p.communicate(input) + stdout = self.translate_newlines(stdout) + stderr = self.translate_newlines(stderr) + return stdout, stderr, p.returncode + + def popen_python(self, input, status=0, stdout="", stderr="", python=None): + if python is None: + python = sys.executable + _stdout, _stderr, _status = self.call_python(input, python) + _stdout = self.translate_newlines(_stdout) + _stderr = self.translate_newlines(_stderr) + assert _status == status, \ + "status = %s, expected %s\n" % (str(_status), str(status)) + \ + "STDOUT ===================\n" + _stdout + \ + "STDERR ===================\n" + _stderr + assert _stdout == stdout, \ + "Expected STDOUT ==========\n" + stdout + \ + "Actual STDOUT ============\n" + _stdout + \ + "STDERR ===================\n" + _stderr + assert _stderr == stderr, \ + "Expected STDERR ==========\n" + stderr + \ + "Actual STDERR ============\n" + _stderr + + def run_match(self, content, *args): + expect = "%s: %s: %s: %s\n" % args + content = self.translate_newlines(content) + assert content == expect, \ + "Expected %s ==========\n" % args[1] + expect + \ + "Actual %s ============\n" % args[1] + content + + + +class __init__TestCase(TestCmdTestCase): + def test_init(self): + """Test init()""" + test = TestCmd.TestCmd() + test = TestCmd.TestCmd(description = 'test') + test = TestCmd.TestCmd(description = 'test', program = 'foo') + test = TestCmd.TestCmd(description = 'test', + program = 'foo', + universal_newlines=None) + + + +class basename_TestCase(TestCmdTestCase): + def test_basename(self): + """Test basename() [XXX TO BE WRITTEN]""" + assert 1 == 1 + + + +class cleanup_TestCase(TestCmdTestCase): + def test_cleanup(self): + """Test cleanup()""" + test = TestCmd.TestCmd(workdir = '') + wdir = test.workdir + test.write('file1', "Test file #1\n") + test.cleanup() + assert not os.path.exists(wdir) + + def test_writable(self): + """Test cleanup() when the directory isn't writable""" + test = TestCmd.TestCmd(workdir = '') + wdir = test.workdir + test.write('file2', "Test file #2\n") + os.chmod(test.workpath('file2'), 0o400) + os.chmod(wdir, 0o500) + test.cleanup() + assert not os.path.exists(wdir) + + def test_shutil(self): + """Test cleanup() when used with shutil""" + test = TestCmd.TestCmd(workdir = '') + wdir = test.workdir + os.chdir(wdir) + + import shutil + save_rmtree = shutil.rmtree + def my_rmtree(dir, ignore_errors=0, wdir=wdir, _rmtree=save_rmtree): + assert os.getcwd() != wdir + return _rmtree(dir, ignore_errors=ignore_errors) + try: + shutil.rmtree = my_rmtree + test.cleanup() + finally: + shutil.rmtree = save_rmtree + + def test_atexit(self): + """Test cleanup() when atexit is used""" + self.popen_python("""from __future__ import print_function +import sys +sys.path = ['%s'] + sys.path +import atexit +def my_exitfunc(): + print("my_exitfunc()") +atexit.register(my_exitfunc) +import TestCmd +result = TestCmd.TestCmd(workdir = '') +sys.exit(0) +""" % self.orig_cwd, stdout='my_exitfunc()\n') + + def test_exitfunc(self): + """Test cleanup() when sys.exitfunc is set""" + self.popen_python("""from __future__ import print_function +import sys +sys.path = ['%s'] + sys.path +def my_exitfunc(): + print("my_exitfunc()") +sys.exitfunc = my_exitfunc +import TestCmd +result = TestCmd.TestCmd(workdir = '') +sys.exit(0) +""" % self.orig_cwd, stdout='my_exitfunc()\n') + + + +class chmod_TestCase(TestCmdTestCase): + def test_chmod(self): + """Test chmod()""" + test = TestCmd.TestCmd(workdir = '', subdir = 'sub') + + wdir_file1 = os.path.join(test.workdir, 'file1') + wdir_sub_file2 = os.path.join(test.workdir, 'sub', 'file2') + + open(wdir_file1, 'w').write("") + open(wdir_sub_file2, 'w').write("") + + if sys.platform == 'win32': + + test.chmod(wdir_file1, stat.S_IREAD) + test.chmod(['sub', 'file2'], stat.S_IWRITE) + + file1_mode = stat.S_IMODE(os.stat(wdir_file1)[stat.ST_MODE]) + assert file1_mode == 0o444, '0%o' % file1_mode + file2_mode = stat.S_IMODE(os.stat(wdir_sub_file2)[stat.ST_MODE]) + assert file2_mode == 0o666, '0%o' % file2_mode + + test.chmod('file1', stat.S_IWRITE) + test.chmod(wdir_sub_file2, stat.S_IREAD) + + file1_mode = stat.S_IMODE(os.stat(wdir_file1)[stat.ST_MODE]) + assert file1_mode == 0o666, '0%o' % file1_mode + file2_mode = stat.S_IMODE(os.stat(wdir_sub_file2)[stat.ST_MODE]) + assert file2_mode == 0o444, '0%o' % file2_mode + + else: + + test.chmod(wdir_file1, 0o700) + test.chmod(['sub', 'file2'], 0o760) + + file1_mode = stat.S_IMODE(os.stat(wdir_file1)[stat.ST_MODE]) + assert file1_mode == 0o700, '0%o' % file1_mode + file2_mode = stat.S_IMODE(os.stat(wdir_sub_file2)[stat.ST_MODE]) + assert file2_mode == 0o760, '0%o' % file2_mode + + test.chmod('file1', 0o765) + test.chmod(wdir_sub_file2, 0o567) + + file1_mode = stat.S_IMODE(os.stat(wdir_file1)[stat.ST_MODE]) + assert file1_mode == 0o765, '0%o' % file1_mode + file2_mode = stat.S_IMODE(os.stat(wdir_sub_file2)[stat.ST_MODE]) + assert file2_mode == 0o567, '0%o' % file2_mode + + + +class combine_TestCase(TestCmdTestCase): + def test_combine(self): + """Test combining stdout and stderr""" + run_env = TestCmd.TestCmd(workdir = '') + run_env.write('run1', """import sys +sys.stdout.write("run1 STDOUT %s\\n" % sys.argv[1:]) +sys.stdout.write("run1 STDOUT second line\\n") +sys.stderr.write("run1 STDERR %s\\n" % sys.argv[1:]) +sys.stderr.write("run1 STDERR second line\\n") +sys.stdout.write("run1 STDOUT third line\\n") +sys.stderr.write("run1 STDERR third line\\n") +""") + run_env.write('run2', """import sys +sys.stdout.write("run2 STDOUT %s\\n" % sys.argv[1:]) +sys.stdout.write("run2 STDOUT second line\\n") +sys.stderr.write("run2 STDERR %s\\n" % sys.argv[1:]) +sys.stderr.write("run2 STDERR second line\\n") +sys.stdout.write("run2 STDOUT third line\\n") +sys.stderr.write("run2 STDERR third line\\n") +""") + cwd = os.getcwd() + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + try: + test = TestCmd.TestCmd(interpreter = 'python', + workdir = '', + combine = 1) + try: + output = test.stdout() + except IndexError: + pass + else: + raise IndexError("got unexpected output:\n\t`%s'\n" % output) + + # The underlying system subprocess implementations can combine + # stdout and stderr in different orders, so we accomodate both. + + test.program_set('run1') + test.run(arguments = 'foo bar') + stdout_lines = """\ +run1 STDOUT ['foo', 'bar'] +run1 STDOUT second line +run1 STDOUT third line +""" + stderr_lines = """\ +run1 STDERR ['foo', 'bar'] +run1 STDERR second line +run1 STDERR third line +""" + foo_bar_expect = (stdout_lines + stderr_lines, + stderr_lines + stdout_lines) + + test.program_set('run2') + test.run(arguments = 'snafu') + stdout_lines = """\ +run2 STDOUT ['snafu'] +run2 STDOUT second line +run2 STDOUT third line +""" + stderr_lines = """\ +run2 STDERR ['snafu'] +run2 STDERR second line +run2 STDERR third line +""" + snafu_expect = (stdout_lines + stderr_lines, + stderr_lines + stdout_lines) + + # XXX SHOULD TEST ABSOLUTE NUMBER AS WELL + output = test.stdout() + output = self.translate_newlines(output) + assert output in snafu_expect, output + error = test.stderr() + assert error == '', error + + output = test.stdout(run = -1) + output = self.translate_newlines(output) + assert output in foo_bar_expect, output + error = test.stderr(-1) + assert error == '', error + finally: + os.chdir(cwd) + + + +class description_TestCase(TestCmdTestCase): + def test_description(self): + """Test description()""" + test = TestCmd.TestCmd() + assert test.description is None, 'initialized description?' + test = TestCmd.TestCmd(description = 'test') + assert test.description == 'test', 'uninitialized description' + test.description_set('foo') + assert test.description == 'foo', 'did not set description' + + + +class diff_TestCase(TestCmdTestCase): + def test_diff_re(self): + """Test diff_re()""" + result = TestCmd.diff_re(["abcde"], ["abcde"]) + assert result == [], result + result = TestCmd.diff_re(["a.*e"], ["abcde"]) + assert result == [], result + result = TestCmd.diff_re(["a.*e"], ["xxx"]) + assert result == ['1c1', "< 'a.*e'", '---', "> 'xxx'"], result + + def test_diff_custom_function(self): + """Test diff() using a custom function""" + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +def my_diff(a, b): + return [ + '*****', + a, + '*****', + b, + '*****', + ] +test = TestCmd.TestCmd(diff = my_diff) +test.diff("a\\nb1\\nc\\n", "a\\nb2\\nc\\n", "STDOUT") +sys.exit(0) +""" % self.orig_cwd, + stdout = """\ +STDOUT========================================================================== +***** +['a', 'b1', 'c'] +***** +['a', 'b2', 'c'] +***** +""") + + def test_diff_string(self): + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(diff = 'diff_re') +test.diff("a\\nb1\\nc\\n", "a\\nb2\\nc\\n", 'STDOUT') +sys.exit(0) +""" % self.orig_cwd, + stdout = """\ +STDOUT========================================================================== +2c2 +< 'b1' +--- +> 'b2' +""") + + def test_error(self): + """Test handling a compilation error in TestCmd.diff_re()""" + script_input = """import sys +sys.path = ['%s'] + sys.path +import TestCmd +assert TestCmd.diff_re(["a.*(e"], ["abcde"]) +sys.exit(0) +""" % self.orig_cwd + stdout, stderr, status = self.call_python(script_input) + assert status == 1, status + expect1 = "Regular expression error in '^a.*(e$': missing )\n" + expect2 = "Regular expression error in '^a.*(e$': unbalanced parenthesis\n" + assert (stderr.find(expect1) != -1 or + stderr.find(expect2) != -1), repr(stderr) + + def test_simple_diff_static_method(self): + """Test calling the TestCmd.TestCmd.simple_diff() static method""" + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +result = TestCmd.TestCmd.simple_diff(['a', 'b', 'c', 'e', 'f1'], + ['a', 'c', 'd', 'e', 'f2']) +expect = ['2d1', '< b', '3a3', '> d', '5c5', '< f1', '---', '> f2'] +assert result == expect, result +sys.exit(0) +""" % self.orig_cwd) + + def test_context_diff_static_method(self): + """Test calling the TestCmd.TestCmd.context_diff() static method""" + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +result = TestCmd.TestCmd.context_diff(['a\\n', 'b\\n', 'c\\n', 'e\\n', 'f1\\n'], + ['a\\n', 'c\\n', 'd\\n', 'e\\n', 'f2\\n']) +result = list(result) +expect = [ + '*** \\n', + '--- \\n', + '***************\\n', + '*** 1,5 ****\\n', + ' a\\n', + '- b\\n', + ' c\\n', + ' e\\n', + '! f1\\n', + '--- 1,5 ----\\n', + ' a\\n', + ' c\\n', + '+ d\\n', + ' e\\n', + '! f2\\n', +] +assert result == expect, result +sys.exit(0) +""" % self.orig_cwd) + + def test_unified_diff_static_method(self): + """Test calling the TestCmd.TestCmd.unified_diff() static method""" + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +result = TestCmd.TestCmd.unified_diff(['a\\n', 'b\\n', 'c\\n', 'e\\n', 'f1\\n'], + ['a\\n', 'c\\n', 'd\\n', 'e\\n', 'f2\\n']) +result = list(result) +expect = [ + '--- \\n', + '+++ \\n', + '@@ -1,5 +1,5 @@\\n', + ' a\\n', + '-b\\n', + ' c\\n', + '+d\\n', + ' e\\n', + '-f1\\n', + '+f2\\n' +] +assert result == expect, result +sys.exit(0) +""" % self.orig_cwd) + + def test_diff_re_static_method(self): + """Test calling the TestCmd.TestCmd.diff_re() static method""" + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +result = TestCmd.TestCmd.diff_re(['a', 'b', 'c', '.', 'f1'], + ['a', 'c', 'd', 'e', 'f2']) +expect = [ + '2c2', + "< 'b'", + '---', + "> 'c'", + '3c3', + "< 'c'", + '---', + "> 'd'", + '5c5', + "< 'f1'", + '---', + "> 'f2'" +] +assert result == expect, result +sys.exit(0) +""" % self.orig_cwd) + + + +class diff_stderr_TestCase(TestCmdTestCase): + def test_diff_stderr_default(self): + """Test diff_stderr() default behavior""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd() +test.diff_stderr('a\nb1\nc\n', 'a\nb2\nc\n') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +2c2 +< b1 +--- +> b2 +""") + + def test_diff_stderr_not_affecting_diff_stdout(self): + """Test diff_stderr() not affecting diff_stdout() behavior""" + self.popen_python(r"""from __future__ import print_function +import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(diff_stderr='diff_re') +print("diff_stderr:") +test.diff_stderr('a\nb.\nc\n', 'a\nbb\nc\n') +print("diff_stdout:") +test.diff_stdout('a\nb.\nc\n', 'a\nbb\nc\n') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +diff_stderr: +diff_stdout: +2c2 +< b. +--- +> bb +""") + + def test_diff_stderr_custom_function(self): + """Test diff_stderr() using a custom function""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +def my_diff(a, b): + return ["a:"] + a + ["b:"] + b +test = TestCmd.TestCmd(diff_stderr=my_diff) +test.diff_stderr('abc', 'def') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +a: +abc +b: +def +""") + + def test_diff_stderr_TestCmd_function(self): + """Test diff_stderr() using a TestCmd function""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(diff_stderr = TestCmd.diff_re) +test.diff_stderr('a\n.\n', 'b\nc\n') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +1c1 +< 'a' +--- +> 'b' +""") + + def test_diff_stderr_static_method(self): + """Test diff_stderr() using a static method""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(diff_stderr=TestCmd.TestCmd.diff_re) +test.diff_stderr('a\n.\n', 'b\nc\n') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +1c1 +< 'a' +--- +> 'b' +""") + + def test_diff_stderr_string(self): + """Test diff_stderr() using a string to fetch the diff method""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(diff_stderr='diff_re') +test.diff_stderr('a\n.\n', 'b\nc\n') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +1c1 +< 'a' +--- +> 'b' +""") + + + +class diff_stdout_TestCase(TestCmdTestCase): + def test_diff_stdout_default(self): + """Test diff_stdout() default behavior""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd() +test.diff_stdout('a\nb1\nc\n', 'a\nb2\nc\n') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +2c2 +< b1 +--- +> b2 +""") + + def test_diff_stdout_not_affecting_diff_stderr(self): + """Test diff_stdout() not affecting diff_stderr() behavior""" + self.popen_python(r"""from __future__ import print_function +import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(diff_stdout='diff_re') +print("diff_stdout:") +test.diff_stdout('a\nb.\nc\n', 'a\nbb\nc\n') +print("diff_stderr:") +test.diff_stderr('a\nb.\nc\n', 'a\nbb\nc\n') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +diff_stdout: +diff_stderr: +2c2 +< b. +--- +> bb +""") + + def test_diff_stdout_custom_function(self): + """Test diff_stdout() using a custom function""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +def my_diff(a, b): + return ["a:"] + a + ["b:"] + b +test = TestCmd.TestCmd(diff_stdout=my_diff) +test.diff_stdout('abc', 'def') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +a: +abc +b: +def +""") + + def test_diff_stdout_TestCmd_function(self): + """Test diff_stdout() using a TestCmd function""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(diff_stdout = TestCmd.diff_re) +test.diff_stdout('a\n.\n', 'b\nc\n') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +1c1 +< 'a' +--- +> 'b' +""") + + def test_diff_stdout_static_method(self): + """Test diff_stdout() using a static method""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(diff_stdout=TestCmd.TestCmd.diff_re) +test.diff_stdout('a\n.\n', 'b\nc\n') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +1c1 +< 'a' +--- +> 'b' +""") + + def test_diff_stdout_string(self): + """Test diff_stdout() using a string to fetch the diff method""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(diff_stdout='diff_re') +test.diff_stdout('a\n.\n', 'b\nc\n') +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +1c1 +< 'a' +--- +> 'b' +""") + + + +class exit_TestCase(TestCmdTestCase): + def test_exit(self): + """Test exit()""" + def _test_it(cwd, tempdir, condition, preserved): + close_true = {'pass_test': 1, 'fail_test': 0, 'no_result': 0} + exit_status = {'pass_test': 0, 'fail_test': 1, 'no_result': 2} + result_string = {'pass_test': "PASSED\n", + 'fail_test': "FAILED test at line 5 of \n", + 'no_result': "NO RESULT for test at line 5 of \n"} + global ExitError + input = """import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(workdir = '%s') +test.%s() +""" % (cwd, tempdir, condition) + stdout, stderr, status = self.call_python(input, python="python") + if close_true[condition]: + unexpected = (status != 0) + else: + unexpected = (status == 0) + if unexpected: + msg = "Unexpected exit status from python: %s\n" + raise ExitError(msg % status + stdout + stderr) + if status != exit_status[condition]: + msg = "Expected exit status %d, got %d\n" + raise ExitError(msg % (exit_status[condition], status)) + if stderr != result_string[condition]: + msg = "Expected error output:\n%sGot error output:\n%s" + raise ExitError(msg % (result_string[condition], stderr)) + if preserved: + if not os.path.exists(tempdir): + msg = "Working directory %s was mistakenly removed\n" + raise ExitError(msg % tempdir + stdout) + else: + if os.path.exists(tempdir): + msg = "Working directory %s was mistakenly preserved\n" + raise ExitError(msg % tempdir + stdout) + + run_env = TestCmd.TestCmd(workdir = '') + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + try: + cwd = self.orig_cwd + _clear_dict(os.environ, 'PRESERVE', 'PRESERVE_PASS', 'PRESERVE_FAIL', 'PRESERVE_NO_RESULT') + _test_it(cwd, 'dir01', 'pass_test', 0) + _test_it(cwd, 'dir02', 'fail_test', 0) + _test_it(cwd, 'dir03', 'no_result', 0) + os.environ['PRESERVE'] = '1' + _test_it(cwd, 'dir04', 'pass_test', 1) + _test_it(cwd, 'dir05', 'fail_test', 1) + _test_it(cwd, 'dir06', 'no_result', 1) + os.environ['PRESERVE'] = '' # del os.environ['PRESERVE'] + os.environ['PRESERVE_PASS'] = '1' + _test_it(cwd, 'dir07', 'pass_test', 1) + _test_it(cwd, 'dir08', 'fail_test', 0) + _test_it(cwd, 'dir09', 'no_result', 0) + os.environ['PRESERVE_PASS'] = '' # del os.environ['PRESERVE_PASS'] + os.environ['PRESERVE_FAIL'] = '1' + _test_it(cwd, 'dir10', 'pass_test', 0) + _test_it(cwd, 'dir11', 'fail_test', 1) + _test_it(cwd, 'dir12', 'no_result', 0) + os.environ['PRESERVE_FAIL'] = '' # del os.environ['PRESERVE_FAIL'] + os.environ['PRESERVE_NO_RESULT'] = '1' + _test_it(cwd, 'dir13', 'pass_test', 0) + _test_it(cwd, 'dir14', 'fail_test', 0) + _test_it(cwd, 'dir15', 'no_result', 1) + os.environ['PRESERVE_NO_RESULT'] = '' # del os.environ['PRESERVE_NO_RESULT'] + finally: + _clear_dict(os.environ, 'PRESERVE', 'PRESERVE_PASS', 'PRESERVE_FAIL', 'PRESERVE_NO_RESULT') + + + +class fail_test_TestCase(TestCmdTestCase): + def test_fail_test(self): + """Test fail_test()""" + run_env = TestCmd.TestCmd(workdir = '') + run_env.write('run', """import sys +sys.stdout.write("run: STDOUT\\n") +sys.stderr.write("run: STDERR\\n") +""") + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +TestCmd.fail_test(condition = 1) +""" % self.orig_cwd, status = 1, stderr = "FAILED test at line 4 of \n") + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') +test.run() +test.fail_test(condition = (test.status == 0)) +""" % self.orig_cwd, status = 1, stderr = "FAILED test of %s\n\tat line 6 of \n" % run_env.workpath('run')) + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(program = 'run', interpreter = 'python', description = 'xyzzy', workdir = '') +test.run() +test.fail_test(condition = (test.status == 0)) +""" % self.orig_cwd, status = 1, stderr = "FAILED test of %s [xyzzy]\n\tat line 6 of \n" % run_env.workpath('run')) + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') +test.run() +def xxx(): + sys.stderr.write("printed on failure\\n") +test.fail_test(condition = (test.status == 0), function = xxx) +""" % self.orig_cwd, status = 1, stderr = "printed on failure\nFAILED test of %s\n\tat line 8 of \n" % run_env.workpath('run')) + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +def test1(self): + self.run() + self.fail_test(condition = (self.status == 0)) +def test2(self): + test1(self) +test2(TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '')) +""" % self.orig_cwd, status = 1, stderr = "FAILED test of %s\n\tat line 6 of (test1)\n\tfrom line 8 of (test2)\n\tfrom line 9 of \n" % run_env.workpath('run')) + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +def test1(self): + self.run() + self.fail_test(condition = (self.status == 0), skip = 1) +def test2(self): + test1(self) +test2(TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '')) +""" % self.orig_cwd, status = 1, stderr = "FAILED test of %s\n\tat line 8 of (test2)\n\tfrom line 9 of \n" % run_env.workpath('run')) + + + +class interpreter_TestCase(TestCmdTestCase): + def test_interpreter(self): + """Test interpreter()""" + run_env = TestCmd.TestCmd(workdir = '') + run_env.write('run', """import sys +sys.stdout.write("run: STDOUT\\n") +sys.stderr.write("run: STDERR\\n") +""") + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + test = TestCmd.TestCmd(program = 'run', workdir = '') + test.interpreter_set('foo') + assert test.interpreter == 'foo', 'did not set interpreter' + test.interpreter_set('python') + assert test.interpreter == 'python', 'did not set interpreter' + test.run() + + + +class match_TestCase(TestCmdTestCase): + def test_match_default(self): + """Test match() default behavior""" + test = TestCmd.TestCmd() + assert test.match("abcde\n", "a.*e\n") + assert test.match("12345\nabcde\n", "1\\d+5\na.*e\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert test.match(lines, regexes) + + def test_match_custom_function(self): + """Test match() using a custom function""" + def match_length(lines, matches): + return len(lines) == len(matches) + test = TestCmd.TestCmd(match=match_length) + assert not test.match("123\n", "1\n") + assert test.match("123\n", "111\n") + assert not test.match("123\n123\n", "1\n1\n") + assert test.match("123\n123\n", "111\n111\n") + lines = ["123\n", "123\n"] + regexes = ["1\n", "1\n"] + assert test.match(lines, regexes) # due to equal numbers of lines + + def test_match_TestCmd_function(self): + """Test match() using a TestCmd function""" + test = TestCmd.TestCmd(match = TestCmd.match_exact) + assert not test.match("abcde\n", "a.*e\n") + assert test.match("abcde\n", "abcde\n") + assert not test.match("12345\nabcde\n", "1\d+5\na.*e\n") + assert test.match("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match(lines, regexes) + assert test.match(lines, lines) + + def test_match_static_method(self): + """Test match() using a static method""" + test = TestCmd.TestCmd(match=TestCmd.TestCmd.match_exact) + assert not test.match("abcde\n", "a.*e\n") + assert test.match("abcde\n", "abcde\n") + assert not test.match("12345\nabcde\n", "1\d+5\na.*e\n") + assert test.match("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match(lines, regexes) + assert test.match(lines, lines) + + def test_match_string(self): + """Test match() using a string to fetch the match method""" + test = TestCmd.TestCmd(match='match_exact') + assert not test.match("abcde\n", "a.*e\n") + assert test.match("abcde\n", "abcde\n") + assert not test.match("12345\nabcde\n", "1\d+5\na.*e\n") + assert test.match("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match(lines, regexes) + assert test.match(lines, lines) + + + +class match_exact_TestCase(TestCmdTestCase): + def test_match_exact_function(self): + """Test calling the TestCmd.match_exact() function""" + assert not TestCmd.match_exact("abcde\\n", "a.*e\\n") + assert TestCmd.match_exact("abcde\\n", "abcde\\n") + + def test_match_exact_instance_method(self): + """Test calling the TestCmd.TestCmd().match_exact() instance method""" + test = TestCmd.TestCmd() + assert not test.match_exact("abcde\\n", "a.*e\\n") + assert test.match_exact("abcde\\n", "abcde\\n") + + def test_match_exact_static_method(self): + """Test calling the TestCmd.TestCmd.match_exact() static method""" + assert not TestCmd.TestCmd.match_exact("abcde\\n", "a.*e\\n") + assert TestCmd.TestCmd.match_exact("abcde\\n", "abcde\\n") + + def test_evaluation(self): + """Test match_exact() evaluation""" + test = TestCmd.TestCmd() + assert not test.match_exact("abcde\n", "a.*e\n") + assert test.match_exact("abcde\n", "abcde\n") + assert not test.match_exact(["12345\n", "abcde\n"], ["1[0-9]*5\n", "a.*e\n"]) + assert test.match_exact(["12345\n", "abcde\n"], ["12345\n", "abcde\n"]) + assert not test.match_exact(UserList(["12345\n", "abcde\n"]), + ["1[0-9]*5\n", "a.*e\n"]) + assert test.match_exact(UserList(["12345\n", "abcde\n"]), + ["12345\n", "abcde\n"]) + assert not test.match_exact(["12345\n", "abcde\n"], + UserList(["1[0-9]*5\n", "a.*e\n"])) + assert test.match_exact(["12345\n", "abcde\n"], + UserList(["12345\n", "abcde\n"])) + assert not test.match_exact("12345\nabcde\n", "1[0-9]*5\na.*e\n") + assert test.match_exact("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match_exact(lines, regexes) + assert test.match_exact(lines, lines) + + + +class match_re_dotall_TestCase(TestCmdTestCase): + def test_match_re_dotall_function(self): + """Test calling the TestCmd.match_re_dotall() function""" + assert TestCmd.match_re_dotall("abcde\nfghij\n", "a.*j\n") + + def test_match_re_dotall_instance_method(self): + """Test calling the TestCmd.TestCmd().match_re_dotall() instance method""" + test = TestCmd.TestCmd() + test.match_re_dotall("abcde\\nfghij\\n", "a.*j\\n") + + def test_match_re_dotall_static_method(self): + """Test calling the TestCmd.TestCmd.match_re_dotall() static method""" + assert TestCmd.TestCmd.match_re_dotall("abcde\nfghij\n", "a.*j\n") + + def test_error(self): + """Test handling a compilation error in TestCmd.match_re_dotall()""" + run_env = TestCmd.TestCmd(workdir = '') + cwd = os.getcwd() + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + try: + script_input = """import sys +sys.path = ['%s'] + sys.path +import TestCmd +assert TestCmd.match_re_dotall("abcde", "a.*(e") +sys.exit(0) +""" % cwd + stdout, stderr, status = self.call_python(script_input) + assert status == 1, status + expect1 = "Regular expression error in '^a.*(e$': missing )\n" + expect2 = "Regular expression error in '^a.*(e$': unbalanced parenthesis\n" + assert (stderr.find(expect1) != -1 or + stderr.find(expect2) != -1), repr(stderr) + finally: + os.chdir(cwd) + + def test_evaluation(self): + """Test match_re_dotall() evaluation""" + test = TestCmd.TestCmd() + assert test.match_re_dotall("abcde\nfghij\n", "a.*e\nf.*j\n") + assert test.match_re_dotall("abcde\nfghij\n", "a[^j]*j\n") + assert test.match_re_dotall("abcde\nfghij\n", "abcde\nfghij\n") + assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], + ["1[0-9]*5\n", "a.*e\n", "f.*j\n"]) + assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], + ["1.*j\n"]) + assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], + ["12345\n", "abcde\n", "fghij\n"]) + assert test.match_re_dotall(UserList(["12345\n", + "abcde\n", + "fghij\n"]), + ["1[0-9]*5\n", "a.*e\n", "f.*j\n"]) + assert test.match_re_dotall(UserList(["12345\n", + "abcde\n", + "fghij\n"]), + ["1.*j\n"]) + assert test.match_re_dotall(UserList(["12345\n", + "abcde\n", + "fghij\n"]), + ["12345\n", "abcde\n", "fghij\n"]) + assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], + UserList(["1[0-9]*5\n", + "a.*e\n", + "f.*j\n"])) + assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], + UserList(["1.*j\n"])) + assert test.match_re_dotall(["12345\n", "abcde\n", "fghij\n"], + UserList(["12345\n", + "abcde\n", + "fghij\n"])) + assert test.match_re_dotall("12345\nabcde\nfghij\n", + "1[0-9]*5\na.*e\nf.*j\n") + assert test.match_re_dotall("12345\nabcde\nfghij\n", "1.*j\n") + assert test.match_re_dotall("12345\nabcde\nfghij\n", + "12345\nabcde\nfghij\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert test.match_re_dotall(lines, regexes) + assert test.match_re_dotall(lines, lines) + + + +class match_re_TestCase(TestCmdTestCase): + def test_match_re_function(self): + """Test calling the TestCmd.match_re() function""" + assert TestCmd.match_re("abcde\n", "a.*e\n") + + def test_match_re_instance_method(self): + """Test calling the TestCmd.TestCmd().match_re() instance method""" + test = TestCmd.TestCmd() + assert test.match_re("abcde\n", "a.*e\n") + + def test_match_re_static_method(self): + """Test calling the TestCmd.TestCmd.match_re() static method""" + assert TestCmd.TestCmd.match_re("abcde\n", "a.*e\n") + + def test_error(self): + """Test handling a compilation error in TestCmd.match_re()""" + run_env = TestCmd.TestCmd(workdir = '') + cwd = os.getcwd() + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + try: + script_input = """import sys +sys.path = ['%s'] + sys.path +import TestCmd +assert TestCmd.match_re("abcde\\n", "a.*(e\\n") +sys.exit(0) +""" % cwd + stdout, stderr, status = self.call_python(script_input) + assert status == 1, status + expect1 = "Regular expression error in '^a.*(e$': missing )\n" + expect2 = "Regular expression error in '^a.*(e$': unbalanced parenthesis\n" + assert (stderr.find(expect1) != -1 or + stderr.find(expect2) != -1), repr(stderr) + finally: + os.chdir(cwd) + + def test_evaluation(self): + """Test match_re() evaluation""" + test = TestCmd.TestCmd() + assert test.match_re("abcde\n", "a.*e\n") + assert test.match_re("abcde\n", "abcde\n") + assert test.match_re(["12345\n", "abcde\n"], ["1[0-9]*5\n", "a.*e\n"]) + assert test.match_re(["12345\n", "abcde\n"], ["12345\n", "abcde\n"]) + assert test.match_re(UserList(["12345\n", "abcde\n"]), + ["1[0-9]*5\n", "a.*e\n"]) + assert test.match_re(UserList(["12345\n", "abcde\n"]), + ["12345\n", "abcde\n"]) + assert test.match_re(["12345\n", "abcde\n"], + UserList(["1[0-9]*5\n", "a.*e\n"])) + assert test.match_re(["12345\n", "abcde\n"], + UserList(["12345\n", "abcde\n"])) + assert test.match_re("12345\nabcde\n", "1[0-9]*5\na.*e\n") + assert test.match_re("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert test.match_re(lines, regexes) + assert test.match_re(lines, lines) + + + +class match_stderr_TestCase(TestCmdTestCase): + def test_match_stderr_default(self): + """Test match_stderr() default behavior""" + test = TestCmd.TestCmd() + assert test.match_stderr("abcde\n", "a.*e\n") + assert test.match_stderr("12345\nabcde\n", "1\\d+5\na.*e\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert test.match_stderr(lines, regexes) + + def test_match_stderr_not_affecting_match_stdout(self): + """Test match_stderr() not affecting match_stdout() behavior""" + test = TestCmd.TestCmd(match_stderr=TestCmd.TestCmd.match_exact) + + assert not test.match_stderr("abcde\n", "a.*e\n") + assert test.match_stderr("abcde\n", "abcde\n") + assert not test.match_stderr("12345\nabcde\n", "1\\d+5\na.*e\n") + assert test.match_stderr("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match_stderr(lines, regexes) + assert test.match_stderr(lines, lines) + + assert test.match_stdout("abcde\n", "a.*e\n") + assert test.match_stdout("12345\nabcde\n", "1\\d+5\na.*e\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert test.match_stdout(lines, regexes) + + def test_match_stderr_custom_function(self): + """Test match_stderr() using a custom function""" + def match_length(lines, matches): + return len(lines) == len(matches) + test = TestCmd.TestCmd(match_stderr=match_length) + assert not test.match_stderr("123\n", "1\n") + assert test.match_stderr("123\n", "111\n") + assert not test.match_stderr("123\n123\n", "1\n1\n") + assert test.match_stderr("123\n123\n", "111\n111\n") + lines = ["123\n", "123\n"] + regexes = ["1\n", "1\n"] + assert test.match_stderr(lines, regexes) # equal numbers of lines + + def test_match_stderr_TestCmd_function(self): + """Test match_stderr() using a TestCmd function""" + test = TestCmd.TestCmd(match_stderr = TestCmd.match_exact) + assert not test.match_stderr("abcde\n", "a.*e\n") + assert test.match_stderr("abcde\n", "abcde\n") + assert not test.match_stderr("12345\nabcde\n", "1\d+5\na.*e\n") + assert test.match_stderr("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match_stderr(lines, regexes) + assert test.match_stderr(lines, lines) + + def test_match_stderr_static_method(self): + """Test match_stderr() using a static method""" + test = TestCmd.TestCmd(match_stderr=TestCmd.TestCmd.match_exact) + assert not test.match_stderr("abcde\n", "a.*e\n") + assert test.match_stderr("abcde\n", "abcde\n") + assert not test.match_stderr("12345\nabcde\n", "1\d+5\na.*e\n") + assert test.match_stderr("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match_stderr(lines, regexes) + assert test.match_stderr(lines, lines) + + def test_match_stderr_string(self): + """Test match_stderr() using a string to fetch the match method""" + test = TestCmd.TestCmd(match_stderr='match_exact') + assert not test.match_stderr("abcde\n", "a.*e\n") + assert test.match_stderr("abcde\n", "abcde\n") + assert not test.match_stderr("12345\nabcde\n", "1\d+5\na.*e\n") + assert test.match_stderr("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match_stderr(lines, regexes) + assert test.match_stderr(lines, lines) + + + +class match_stdout_TestCase(TestCmdTestCase): + def test_match_stdout_default(self): + """Test match_stdout() default behavior""" + test = TestCmd.TestCmd() + assert test.match_stdout("abcde\n", "a.*e\n") + assert test.match_stdout("12345\nabcde\n", "1\\d+5\na.*e\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert test.match_stdout(lines, regexes) + + def test_match_stdout_not_affecting_match_stderr(self): + """Test match_stdout() not affecting match_stderr() behavior""" + test = TestCmd.TestCmd(match_stdout=TestCmd.TestCmd.match_exact) + + assert not test.match_stdout("abcde\n", "a.*e\n") + assert test.match_stdout("abcde\n", "abcde\n") + assert not test.match_stdout("12345\nabcde\n", "1\\d+5\na.*e\n") + assert test.match_stdout("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match_stdout(lines, regexes) + assert test.match_stdout(lines, lines) + + assert test.match_stderr("abcde\n", "a.*e\n") + assert test.match_stderr("12345\nabcde\n", "1\\d+5\na.*e\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert test.match_stderr(lines, regexes) + + def test_match_stdout_custom_function(self): + """Test match_stdout() using a custom function""" + def match_length(lines, matches): + return len(lines) == len(matches) + test = TestCmd.TestCmd(match_stdout=match_length) + assert not test.match_stdout("123\n", "1\n") + assert test.match_stdout("123\n", "111\n") + assert not test.match_stdout("123\n123\n", "1\n1\n") + assert test.match_stdout("123\n123\n", "111\n111\n") + lines = ["123\n", "123\n"] + regexes = ["1\n", "1\n"] + assert test.match_stdout(lines, regexes) # equal numbers of lines + + def test_match_stdout_TestCmd_function(self): + """Test match_stdout() using a TestCmd function""" + test = TestCmd.TestCmd(match_stdout = TestCmd.match_exact) + assert not test.match_stdout("abcde\n", "a.*e\n") + assert test.match_stdout("abcde\n", "abcde\n") + assert not test.match_stdout("12345\nabcde\n", "1\d+5\na.*e\n") + assert test.match_stdout("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match_stdout(lines, regexes) + assert test.match_stdout(lines, lines) + + def test_match_stdout_static_method(self): + """Test match_stdout() using a static method""" + test = TestCmd.TestCmd(match_stdout=TestCmd.TestCmd.match_exact) + assert not test.match_stdout("abcde\n", "a.*e\n") + assert test.match_stdout("abcde\n", "abcde\n") + assert not test.match_stdout("12345\nabcde\n", "1\d+5\na.*e\n") + assert test.match_stdout("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match_stdout(lines, regexes) + assert test.match_stdout(lines, lines) + + def test_match_stdout_string(self): + """Test match_stdout() using a string to fetch the match method""" + test = TestCmd.TestCmd(match_stdout='match_exact') + assert not test.match_stdout("abcde\n", "a.*e\n") + assert test.match_stdout("abcde\n", "abcde\n") + assert not test.match_stdout("12345\nabcde\n", "1\d+5\na.*e\n") + assert test.match_stdout("12345\nabcde\n", "12345\nabcde\n") + lines = ["vwxyz\n", "67890\n"] + regexes = ["v[^a-u]*z\n", "6[^ ]+0\n"] + assert not test.match_stdout(lines, regexes) + assert test.match_stdout(lines, lines) + + + +class no_result_TestCase(TestCmdTestCase): + def test_no_result(self): + """Test no_result()""" + run_env = TestCmd.TestCmd(workdir = '') + run_env.write('run', """import sys +sys.stdout.write("run: STDOUT\\n") +sys.stderr.write("run: STDERR\\n") +""") + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +TestCmd.no_result(condition = 1) +""" % self.orig_cwd, status = 2, stderr = "NO RESULT for test at line 4 of \n") + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') +test.run() +test.no_result(condition = (test.status == 0)) +""" % self.orig_cwd, status = 2, stderr = "NO RESULT for test of %s\n\tat line 6 of \n" % run_env.workpath('run')) + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(program = 'run', interpreter = 'python', description = 'xyzzy', workdir = '') +test.run() +test.no_result(condition = (test.status == 0)) +""" % self.orig_cwd, status = 2, stderr = "NO RESULT for test of %s [xyzzy]\n\tat line 6 of \n" % run_env.workpath('run')) + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') +test.run() +def xxx(): + sys.stderr.write("printed on no result\\n") +test.no_result(condition = (test.status == 0), function = xxx) +""" % self.orig_cwd, status = 2, stderr = "printed on no result\nNO RESULT for test of %s\n\tat line 8 of \n" % run_env.workpath('run')) + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +def test1(self): + self.run() + self.no_result(condition = (self.status == 0)) +def test2(self): + test1(self) +test2(TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '')) +""" % self.orig_cwd, status = 2, stderr = "NO RESULT for test of %s\n\tat line 6 of (test1)\n\tfrom line 8 of (test2)\n\tfrom line 9 of \n" % run_env.workpath('run')) + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +def test1(self): + self.run() + self.no_result(condition = (self.status == 0), skip = 1) +def test2(self): + test1(self) +test2(TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '')) +""" % self.orig_cwd, status = 2, stderr = "NO RESULT for test of %s\n\tat line 8 of (test2)\n\tfrom line 9 of \n" % run_env.workpath('run')) + + + +class pass_test_TestCase(TestCmdTestCase): + def test_pass_test(self): + """Test pass_test()""" + run_env = TestCmd.TestCmd(workdir = '') + run_env.write('run', """import sys +sys.stdout.write("run: STDOUT\\n") +sys.stderr.write("run: STDERR\\n") +""") + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +TestCmd.pass_test(condition = 1) +""" % self.orig_cwd, stderr = "PASSED\n") + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') +test.run() +test.pass_test(condition = (test.status == 0)) +""" % self.orig_cwd, stderr = "PASSED\n") + + self.popen_python("""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') +test.run() +def brag(): + sys.stderr.write("printed on success\\n") +test.pass_test(condition = (test.status == 0), function = brag) +""" % self.orig_cwd, stderr = "printed on success\nPASSED\n") + + # TODO(sgk): SHOULD ALSO TEST FAILURE CONDITIONS + + + +class preserve_TestCase(TestCmdTestCase): + def test_preserve(self): + """Test preserve()""" + def cleanup_test(test, cond=None, stdout=""): + io = StringIO() + save = sys.stdout + sys.stdout = io + try: + if cond: + test.cleanup(cond) + else: + test.cleanup() + o = io.getvalue() + assert o == stdout, "o = `%s', stdout = `%s'" % (o, stdout) + finally: + sys.stdout = save + + test = TestCmd.TestCmd(workdir = '') + wdir = test.workdir + try: + test.write('file1', "Test file #1\n") + #test.cleanup() + cleanup_test(test, ) + assert not os.path.exists(wdir) + finally: + if os.path.exists(wdir): + shutil.rmtree(wdir, ignore_errors = 1) + test._dirlist.remove(wdir) + + test = TestCmd.TestCmd(workdir = '') + wdir = test.workdir + try: + test.write('file2', "Test file #2\n") + test.preserve('pass_test') + cleanup_test(test, 'pass_test', "Preserved directory %s\n" % wdir) + assert os.path.isdir(wdir) + cleanup_test(test, 'fail_test') + assert not os.path.exists(wdir) + finally: + if os.path.exists(wdir): + shutil.rmtree(wdir, ignore_errors = 1) + test._dirlist.remove(wdir) + + test = TestCmd.TestCmd(workdir = '') + wdir = test.workdir + try: + test.write('file3', "Test file #3\n") + test.preserve('fail_test') + cleanup_test(test, 'fail_test', "Preserved directory %s\n" % wdir) + assert os.path.isdir(wdir) + cleanup_test(test, 'pass_test') + assert not os.path.exists(wdir) + finally: + if os.path.exists(wdir): + shutil.rmtree(wdir, ignore_errors = 1) + test._dirlist.remove(wdir) + + test = TestCmd.TestCmd(workdir = '') + wdir = test.workdir + try: + test.write('file4', "Test file #4\n") + test.preserve('fail_test', 'no_result') + cleanup_test(test, 'fail_test', "Preserved directory %s\n" % wdir) + assert os.path.isdir(wdir) + cleanup_test(test, 'no_result', "Preserved directory %s\n" % wdir) + assert os.path.isdir(wdir) + cleanup_test(test, 'pass_test') + assert not os.path.exists(wdir) + finally: + if os.path.exists(wdir): + shutil.rmtree(wdir, ignore_errors = 1) + test._dirlist.remove(wdir) + + test = TestCmd.TestCmd(workdir = '') + wdir = test.workdir + try: + test.preserve() + cleanup_test(test, 'pass_test', "Preserved directory %s\n" % wdir) + assert os.path.isdir(wdir) + cleanup_test(test, 'fail_test', "Preserved directory %s\n" % wdir) + assert os.path.isdir(wdir) + cleanup_test(test, 'no_result', "Preserved directory %s\n" % wdir) + assert os.path.isdir(wdir) + finally: + if os.path.exists(wdir): + shutil.rmtree(wdir, ignore_errors = 1) + test._dirlist.remove(wdir) + + + +class program_TestCase(TestCmdTestCase): + def test_program(self): + """Test program()""" + test = TestCmd.TestCmd() + assert test.program is None, 'initialized program?' + test = TestCmd.TestCmd(program = 'test') + assert test.program == os.path.join(os.getcwd(), 'test'), 'uninitialized program' + test.program_set('foo') + assert test.program == os.path.join(os.getcwd(), 'foo'), 'did not set program' + + + +class read_TestCase(TestCmdTestCase): + def test_read(self): + """Test read()""" + test = TestCmd.TestCmd(workdir = '', subdir = 'foo') + wdir_file1 = os.path.join(test.workdir, 'file1') + wdir_file2 = os.path.join(test.workdir, 'file2') + wdir_foo_file3 = os.path.join(test.workdir, 'foo', 'file3') + wdir_file4 = os.path.join(test.workdir, 'file4') + wdir_file5 = os.path.join(test.workdir, 'file5') + + open(wdir_file1, 'wb').write("") + open(wdir_file2, 'wb').write("Test\nfile\n#2.\n") + open(wdir_foo_file3, 'wb').write("Test\nfile\n#3.\n") + open(wdir_file4, 'wb').write("Test\nfile\n#4.\n") + open(wdir_file5, 'wb').write("Test\r\nfile\r\n#5.\r\n") + + try: + contents = test.read('no_file') + except IOError: # expect "No such file or directory" + pass + except: + raise + + try: + test.read(test.workpath('file_x'), mode = 'w') + except ValueError: # expect "mode must begin with 'r' + pass + except: + raise + + def _file_matches(file, contents, expected): + assert contents == expected, \ + "Expected contents of " + str(file) + "==========\n" + \ + expected + \ + "Actual contents of " + str(file) + "============\n" + \ + contents + + _file_matches(wdir_file1, test.read('file1'), "") + _file_matches(wdir_file2, test.read('file2'), "Test\nfile\n#2.\n") + _file_matches(wdir_foo_file3, test.read(['foo', 'file3']), + "Test\nfile\n#3.\n") + _file_matches(wdir_foo_file3, + test.read(UserList(['foo', 'file3'])), + "Test\nfile\n#3.\n") + _file_matches(wdir_file4, test.read('file4', mode = 'r'), + "Test\nfile\n#4.\n") + _file_matches(wdir_file5, test.read('file5', mode = 'rb'), + "Test\r\nfile\r\n#5.\r\n") + + + +class rmdir_TestCase(TestCmdTestCase): + def test_rmdir(self): + """Test rmdir()""" + test = TestCmd.TestCmd(workdir = '') + + try: + test.rmdir(['no', 'such', 'dir']) + except EnvironmentError: + pass + else: + raise Exception("did not catch expected EnvironmentError") + + test.subdir(['sub'], + ['sub', 'dir'], + ['sub', 'dir', 'one']) + + s = test.workpath('sub') + s_d = test.workpath('sub', 'dir') + s_d_o = test.workpath('sub', 'dir', 'one') + + try: + test.rmdir(['sub']) + except EnvironmentError: + pass + else: + raise Exception("did not catch expected EnvironmentError") + + assert os.path.isdir(s_d_o), "%s is gone?" % s_d_o + + try: + test.rmdir(['sub']) + except EnvironmentError: + pass + else: + raise Exception("did not catch expected EnvironmentError") + + assert os.path.isdir(s_d_o), "%s is gone?" % s_d_o + + test.rmdir(['sub', 'dir', 'one']) + + assert not os.path.exists(s_d_o), "%s exists?" % s_d_o + assert os.path.isdir(s_d), "%s is gone?" % s_d + + test.rmdir(['sub', 'dir']) + + assert not os.path.exists(s_d), "%s exists?" % s_d + assert os.path.isdir(s), "%s is gone?" % s + + test.rmdir('sub') + + assert not os.path.exists(s), "%s exists?" % s + + + +class run_TestCase(TestCmdTestCase): + def test_run(self): + """Test run()""" + + t = self.setup_run_scripts() + + # Everything before this prepared our "source directory." + # Now do the real test. + try: + test = TestCmd.TestCmd(program = t.script, + interpreter = 'python', + workdir = '', + subdir = 'script_subdir') + + test.run() + self.run_match(test.stdout(), t.script, "STDOUT", t.workdir, + repr([])) + self.run_match(test.stderr(), t.script, "STDERR", t.workdir, + repr([])) + + test.run(arguments = 'arg1 arg2 arg3') + self.run_match(test.stdout(), t.script, "STDOUT", t.workdir, + repr(['arg1', 'arg2', 'arg3'])) + self.run_match(test.stderr(), t.script, "STDERR", t.workdir, + repr(['arg1', 'arg2', 'arg3'])) + + test.run(program = t.scriptx, arguments = 'foo') + self.run_match(test.stdout(), t.scriptx, "STDOUT", t.workdir, + repr(['foo'])) + self.run_match(test.stderr(), t.scriptx, "STDERR", t.workdir, + repr(['foo'])) + + test.run(chdir = os.curdir, arguments = 'x y z') + self.run_match(test.stdout(), t.script, "STDOUT", test.workdir, + repr(['x', 'y', 'z'])) + self.run_match(test.stderr(), t.script, "STDERR", test.workdir, + repr(['x', 'y', 'z'])) + + test.run(chdir = 'script_subdir') + script_subdir = test.workpath('script_subdir') + self.run_match(test.stdout(), t.script, "STDOUT", script_subdir, + repr([])) + self.run_match(test.stderr(), t.script, "STDERR", script_subdir, + repr([])) + + test.run(program = t.script1, interpreter = ['python', '-x']) + self.run_match(test.stdout(), t.script1, "STDOUT", t.workdir, + repr([])) + self.run_match(test.stderr(), t.script1, "STDERR", t.workdir, + repr([])) + + try: + test.run(chdir = 'no_subdir') + except OSError: + pass + + test.run(program = 'no_script', interpreter = 'python') + assert test.status != None, test.status + + try: + test.run(program = 'no_script', interpreter = 'no_interpreter') + except OSError: + # Python versions that use subprocess throw an OSError + # exception when they try to execute something that + # isn't there. + pass + else: + # Python versions that use os.popen3() or the Popen3 + # class run things through the shell, which just returns + # a non-zero exit status. + assert test.status != None, test.status + + testx = TestCmd.TestCmd(program = t.scriptx, + workdir = '', + subdir = 't.scriptx_subdir') + + testx.run() + self.run_match(testx.stdout(), t.scriptx, "STDOUT", t.workdir, + repr([])) + self.run_match(testx.stderr(), t.scriptx, "STDERR", t.workdir, + repr([])) + + testx.run(arguments = 'foo bar') + self.run_match(testx.stdout(), t.scriptx, "STDOUT", t.workdir, + repr(['foo', 'bar'])) + self.run_match(testx.stderr(), t.scriptx, "STDERR", t.workdir, + repr(['foo', 'bar'])) + + testx.run(program = t.script, interpreter = 'python', arguments = 'bar') + self.run_match(testx.stdout(), t.script, "STDOUT", t.workdir, + repr(['bar'])) + self.run_match(testx.stderr(), t.script, "STDERR", t.workdir, + repr(['bar'])) + + testx.run(chdir = os.curdir, arguments = 'baz') + self.run_match(testx.stdout(), t.scriptx, "STDOUT", testx.workdir, + repr(['baz'])) + self.run_match(testx.stderr(), t.scriptx, "STDERR", testx.workdir, + repr(['baz'])) + + testx.run(chdir = 't.scriptx_subdir') + t.scriptx_subdir = testx.workpath('t.scriptx_subdir') + self.run_match(testx.stdout(), t.scriptx, "STDOUT", t.scriptx_subdir, + repr([])) + self.run_match(testx.stderr(), t.scriptx, "STDERR", t.scriptx_subdir, + repr([])) + + testx.run(program = t.script1, interpreter = ('python', '-x')) + self.run_match(testx.stdout(), t.script1, "STDOUT", t.workdir, + repr([])) + self.run_match(testx.stderr(), t.script1, "STDERR", t.workdir, + repr([])) + + s = os.path.join('.', t.scriptx) + testx.run(program = [s]) + self.run_match(testx.stdout(), t.scriptx, "STDOUT", t.workdir, + repr([])) + self.run_match(testx.stderr(), t.scriptx, "STDERR", t.workdir, + repr([])) + + try: + testx.run(chdir = 'no_subdir') + except OSError: + pass + + try: + testx.run(program = 'no_program') + except OSError: + # Python versions that use subprocess throw an OSError + # exception when they try to execute something that + # isn't there. + pass + else: + # Python versions that use os.popen3() or the Popen3 + # class run things through the shell, which just returns + # a non-zero exit status. + assert test.status != None + + test1 = TestCmd.TestCmd(program = t.script1, + interpreter = ['python', '-x'], + workdir = '') + + test1.run() + self.run_match(test1.stdout(), t.script1, "STDOUT", t.workdir, + repr([])) + self.run_match(test1.stderr(), t.script1, "STDERR", t.workdir, + repr([])) + + finally: + os.chdir(t.orig_cwd) + + def test_run_subclass(self): + """Test run() through a subclass with different signatures""" + + t = self.setup_run_scripts() + + # Everything before this prepared our "source directory." + # Now do the real test. + + class MyTestCmdSubclass(TestCmd.TestCmd): + def start(self, additional_argument=None, **kw): + return TestCmd.TestCmd.start(self, **kw) + + try: + test = MyTestCmdSubclass(program = t.script, + interpreter = 'python', + workdir = '', + subdir = 'script_subdir') + + test.run() + self.run_match(test.stdout(), t.script, "STDOUT", t.workdir, + repr([])) + self.run_match(test.stderr(), t.script, "STDERR", t.workdir, + repr([])) + finally: + os.chdir(t.orig_cwd) + + +class run_verbose_TestCase(TestCmdTestCase): + def test_run_verbose(self): + """Test the run() method's verbose attribute""" + + # Prepare our "source directory." + t = self.setup_run_scripts() + + save_stdout = sys.stderr + save_stderr = sys.stderr + + try: + # Test calling TestCmd() with an explicit verbose = 1. + + test = TestCmd.TestCmd(program = t.script, + interpreter = 'python', + workdir = '', + verbose = 1) + + sys.stdout = StringIO() + sys.stderr = StringIO() + + test.run(arguments = ['arg1 arg2']) + o = sys.stdout.getvalue() + assert o == '', o + e = sys.stderr.getvalue() + expect = 'python "%s" "arg1 arg2"\n' % t.script_path + assert expect == e, (expect, e) + + testx = TestCmd.TestCmd(program = t.scriptx, + workdir = '', + verbose = 1) + + sys.stdout = StringIO() + sys.stderr = StringIO() + + testx.run(arguments = ['arg1 arg2']) + expect = '"%s" "arg1 arg2"\n' % t.scriptx_path + o = sys.stdout.getvalue() + assert o == '', o + e = sys.stderr.getvalue() + assert expect == e, (expect, e) + + # Test calling TestCmd() with an explicit verbose = 2. + + outerr_fmt = """\ +============ STATUS: 0 +============ BEGIN STDOUT (len=%s): +%s============ END STDOUT +============ BEGIN STDERR (len=%s) +%s============ END STDERR +""" + + out_fmt = """\ +============ STATUS: 0 +============ BEGIN STDOUT (len=%s): +%s============ END STDOUT +""" + + err_fmt = """\ +============ STATUS: 0 +============ BEGIN STDERR (len=%s) +%s============ END STDERR +""" + + test = TestCmd.TestCmd(program = t.script, + interpreter = 'python', + workdir = '', + verbose = 2) + + sys.stdout = StringIO() + sys.stderr = StringIO() + + test.run(arguments = ['arg1 arg2']) + + line_fmt = "script: %s: %s: ['arg1 arg2']\n" + stdout_line = line_fmt % ('STDOUT', t.sub_dir) + stderr_line = line_fmt % ('STDERR', t.sub_dir) + expect = outerr_fmt % (len(stdout_line), stdout_line, + len(stderr_line), stderr_line) + o = sys.stdout.getvalue() + assert expect == o, (expect, o) + + expect = 'python "%s" "arg1 arg2"\n' % t.script_path + e = sys.stderr.getvalue() + assert e == expect, (e, expect) + + testx = TestCmd.TestCmd(program = t.scriptx, + workdir = '', + verbose = 2) + + sys.stdout = StringIO() + sys.stderr = StringIO() + + testx.run(arguments = ['arg1 arg2']) + + line_fmt = "scriptx.bat: %s: %s: ['arg1 arg2']\n" + stdout_line = line_fmt % ('STDOUT', t.sub_dir) + stderr_line = line_fmt % ('STDERR', t.sub_dir) + expect = outerr_fmt % (len(stdout_line), stdout_line, + len(stderr_line), stderr_line) + o = sys.stdout.getvalue() + assert expect == o, (expect, o) + + expect = '"%s" "arg1 arg2"\n' % t.scriptx_path + e = sys.stderr.getvalue() + assert e == expect, (e, expect) + + # Test calling TestCmd() with an explicit verbose = 3. + + test = TestCmd.TestCmd(program = t.scriptout, + interpreter = 'python', + workdir = '', + verbose = 2) + + sys.stdout = StringIO() + sys.stderr = StringIO() + + test.run(arguments = ['arg1 arg2']) + + line_fmt = "scriptout: %s: %s: ['arg1 arg2']\n" + stdout_line = line_fmt % ('STDOUT', t.sub_dir) + expect = out_fmt % (len(stdout_line), stdout_line) + o = sys.stdout.getvalue() + assert expect == o, (expect, o) + + e = sys.stderr.getvalue() + expect = 'python "%s" "arg1 arg2"\n' % (t.scriptout_path) + assert e == expect, (e, expect) + + test = TestCmd.TestCmd(program = t.scriptout, + interpreter = 'python', + workdir = '', + verbose = 3) + + sys.stdout = StringIO() + sys.stderr = StringIO() + + test.run(arguments = ['arg1 arg2']) + + line_fmt = "scriptout: %s: %s: ['arg1 arg2']\n" + stdout_line = line_fmt % ('STDOUT', t.sub_dir) + expect = outerr_fmt % (len(stdout_line), stdout_line, + '0', '') + o = sys.stdout.getvalue() + assert expect == o, (expect, o) + + e = sys.stderr.getvalue() + expect = 'python "%s" "arg1 arg2"\n' % (t.scriptout_path) + assert e == expect, (e, expect) + + # Test letting TestCmd() pick up verbose = 2 from the environment. + + os.environ['TESTCMD_VERBOSE'] = '2' + + test = TestCmd.TestCmd(program = t.script, + interpreter = 'python', + workdir = '') + + sys.stdout = StringIO() + sys.stderr = StringIO() + + test.run(arguments = ['arg1 arg2']) + + line_fmt = "script: %s: %s: ['arg1 arg2']\n" + stdout_line = line_fmt % ('STDOUT', t.sub_dir) + stderr_line = line_fmt % ('STDERR', t.sub_dir) + expect = outerr_fmt % (len(stdout_line), stdout_line, + len(stderr_line), stderr_line) + o = sys.stdout.getvalue() + assert expect == o, (expect, o) + + expect = 'python "%s" "arg1 arg2"\n' % t.script_path + e = sys.stderr.getvalue() + assert e == expect, (e, expect) + + testx = TestCmd.TestCmd(program = t.scriptx, + workdir = '') + + sys.stdout = StringIO() + sys.stderr = StringIO() + + testx.run(arguments = ['arg1 arg2']) + + line_fmt = "scriptx.bat: %s: %s: ['arg1 arg2']\n" + stdout_line = line_fmt % ('STDOUT', t.sub_dir) + stderr_line = line_fmt % ('STDERR', t.sub_dir) + expect = outerr_fmt % (len(stdout_line), stdout_line, + len(stderr_line), stderr_line) + o = sys.stdout.getvalue() + assert expect == o, (expect, o) + + expect = '"%s" "arg1 arg2"\n' % t.scriptx_path + e = sys.stderr.getvalue() + assert e == expect, (e, expect) + + # Test letting TestCmd() pick up verbose = 1 from the environment. + + os.environ['TESTCMD_VERBOSE'] = '1' + + test = TestCmd.TestCmd(program = t.script, + interpreter = 'python', + workdir = '', + verbose = 1) + + sys.stdout = StringIO() + sys.stderr = StringIO() + + test.run(arguments = ['arg1 arg2']) + o = sys.stdout.getvalue() + assert o == '', o + e = sys.stderr.getvalue() + expect = 'python "%s" "arg1 arg2"\n' % t.script_path + assert expect == e, (expect, e) + + testx = TestCmd.TestCmd(program = t.scriptx, + workdir = '', + verbose = 1) + + sys.stdout = StringIO() + sys.stderr = StringIO() + + testx.run(arguments = ['arg1 arg2']) + expect = '"%s" "arg1 arg2"\n' % t.scriptx_path + o = sys.stdout.getvalue() + assert o == '', o + e = sys.stderr.getvalue() + assert expect == e, (expect, e) + + finally: + sys.stdout = save_stdout + sys.stderr = save_stderr + os.chdir(t.orig_cwd) + os.environ['TESTCMD_VERBOSE'] = '' + + + +class set_diff_function_TestCase(TestCmdTestCase): + def test_set_diff_function(self): + """Test set_diff_function()""" + self.popen_python(r"""import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd() +test.diff("a\n", "a\n") +test.set_diff_function('diff_re') +test.diff(".\n", "a\n") +sys.exit(0) +""" % self.orig_cwd) + + def test_set_diff_function_stdout(self): + """Test set_diff_function(): stdout""" + self.popen_python("""from __future__ import print_function +import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd() +print("diff:") +test.diff("a\\n", "a\\n") +print("diff_stdout:") +test.diff_stdout("a\\n", "a\\n") +test.set_diff_function(stdout='diff_re') +print("diff:") +test.diff(".\\n", "a\\n") +print("diff_stdout:") +test.diff_stdout(".\\n", "a\\n") +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +diff: +diff_stdout: +diff: +1c1 +< . +--- +> a +diff_stdout: +""") + + def test_set_diff_function_stderr(self): + """Test set_diff_function(): stderr """ + self.popen_python("""from __future__ import print_function +import sys +sys.path = ['%s'] + sys.path +import TestCmd +test = TestCmd.TestCmd() +print("diff:") +test.diff("a\\n", "a\\n") +print("diff_stderr:") +test.diff_stderr("a\\n", "a\\n") +test.set_diff_function(stderr='diff_re') +print("diff:") +test.diff(".\\n", "a\\n") +print("diff_stderr:") +test.diff_stderr(".\\n", "a\\n") +sys.exit(0) +""" % self.orig_cwd, + stdout="""\ +diff: +diff_stderr: +diff: +1c1 +< . +--- +> a +diff_stderr: +""") + + + +class set_match_function_TestCase(TestCmdTestCase): + def test_set_match_function(self): + """Test set_match_function()""" + test = TestCmd.TestCmd() + assert test.match("abcde\n", "a.*e\n") + assert test.match("abcde\n", "abcde\n") + + test.set_match_function('match_exact') + + assert not test.match("abcde\n", "a.*e\n") + assert test.match("abcde\n", "abcde\n") + + def test_set_match_function_stdout(self): + """Test set_match_function(): stdout """ + test = TestCmd.TestCmd() + assert test.match("abcde\n", "a.*e\n") + assert test.match("abcde\n", "abcde\n") + assert test.match_stdout("abcde\n", "a.*e\n") + assert test.match_stdout("abcde\n", "abcde\n") + + test.set_match_function(stdout='match_exact') + + assert test.match("abcde\n", "a.*e\n") + assert test.match("abcde\n", "abcde\n") + assert not test.match_stdout("abcde\n", "a.*e\n") + assert test.match_stdout("abcde\n", "abcde\n") + + def test_set_match_function_stderr(self): + """Test set_match_function(): stderr """ + test = TestCmd.TestCmd() + assert test.match("abcde\n", "a.*e\n") + assert test.match("abcde\n", "abcde\n") + assert test.match_stderr("abcde\n", "a.*e\n") + assert test.match_stderr("abcde\n", "abcde\n") + + test.set_match_function(stderr='match_exact') + + assert test.match("abcde\n", "a.*e\n") + assert test.match("abcde\n", "abcde\n") + assert not test.match_stderr("abcde\n", "a.*e\n") + assert test.match_stderr("abcde\n", "abcde\n") + + + +class sleep_TestCase(TestCmdTestCase): + def test_sleep(self): + """Test sleep()""" + test = TestCmd.TestCmd() + + start = time.time() + test.sleep() + end = time.time() + diff = end - start + assert diff > 0.9, "only slept %f seconds (start %f, end %f), not default" % (diff, start, end) + + start = time.time() + test.sleep(3) + end = time.time() + diff = end - start + assert diff > 2.9, "only slept %f seconds (start %f, end %f), not 3" % (diff, start, end) + + + +class stderr_TestCase(TestCmdTestCase): + def test_stderr(self): + """Test stderr()""" + run_env = TestCmd.TestCmd(workdir = '') + run_env.write('run1', """import sys +sys.stdout.write("run1 STDOUT %s\\n" % sys.argv[1:]) +sys.stdout.write("run1 STDOUT second line\\n") +sys.stderr.write("run1 STDERR %s\\n" % sys.argv[1:]) +sys.stderr.write("run1 STDERR second line\\n") +""") + run_env.write('run2', """import sys +sys.stdout.write("run2 STDOUT %s\\n" % sys.argv[1:]) +sys.stdout.write("run2 STDOUT second line\\n") +sys.stderr.write("run2 STDERR %s\\n" % sys.argv[1:]) +sys.stderr.write("run2 STDERR second line\\n") +""") + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + test = TestCmd.TestCmd(interpreter = 'python', workdir = '') + try: + output = test.stderr() + except IndexError: + pass + else: + raise IndexError("got unexpected output:\n" + output) + test.program_set('run1') + test.run(arguments = 'foo bar') + test.program_set('run2') + test.run(arguments = 'snafu') + # XXX SHOULD TEST ABSOLUTE NUMBER AS WELL + output = test.stderr() + assert output == "run2 STDERR ['snafu']\nrun2 STDERR second line\n", output + output = test.stderr(run = -1) + assert output == "run1 STDERR ['foo', 'bar']\nrun1 STDERR second line\n", output + + + +class command_args_TestCase(TestCmdTestCase): + def test_command_args(self): + """Test command_args()""" + run_env = TestCmd.TestCmd(workdir = '') + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + test = TestCmd.TestCmd(workdir = '') + + r = test.command_args('prog') + expect = [run_env.workpath('prog')] + assert r == expect, (expect, r) + + r = test.command_args(test.workpath('new_prog')) + expect = [test.workpath('new_prog')] + assert r == expect, (expect, r) + + r = test.command_args('prog', 'python') + expect = ['python', run_env.workpath('prog')] + assert r == expect, (expect, r) + + r = test.command_args('prog', 'python', 'arg1 arg2') + expect = ['python', run_env.workpath('prog'), 'arg1', 'arg2'] + assert r == expect, (expect, r) + + test.program_set('default_prog') + default_prog = run_env.workpath('default_prog') + + r = test.command_args() + expect = [default_prog] + assert r == expect, (expect, r) + + r = test.command_args(interpreter='PYTHON') + expect = ['PYTHON', default_prog] + assert r == expect, (expect, r) + + r = test.command_args(interpreter='PYTHON', arguments='arg3 arg4') + expect = ['PYTHON', default_prog, 'arg3', 'arg4'] + assert r == expect, (expect, r) + + test.interpreter_set('default_python') + + r = test.command_args() + expect = ['default_python', default_prog] + assert r == expect, (expect, r) + + r = test.command_args(arguments='arg5 arg6') + expect = ['default_python', default_prog, 'arg5', 'arg6'] + assert r == expect, (expect, r) + + r = test.command_args('new_prog_1') + expect = [run_env.workpath('new_prog_1')] + assert r == expect, (expect, r) + + r = test.command_args(program='new_prog_2') + expect = [run_env.workpath('new_prog_2')] + assert r == expect, (expect, r) + + + +class start_TestCase(TestCmdTestCase): + def setup_run_scripts(self): + t = TestCmdTestCase.setup_run_scripts(self) + t.recv_script = 'script_recv' + t.recv_script_path = t.run_env.workpath(t.sub_dir, t.recv_script) + t.recv_out_path = t.run_env.workpath('script_recv.out') + text = """\ +import os +import sys + +class Unbuffered: + def __init__(self, file): + self.file = file + def write(self, arg): + self.file.write(arg) + self.file.flush() + def __getattr__(self, attr): + return getattr(self.file, attr) + +sys.stdout = Unbuffered(sys.stdout) +sys.stderr = Unbuffered(sys.stderr) + +sys.stdout.write('script_recv: STDOUT\\n') +sys.stderr.write('script_recv: STDERR\\n') +logfp = open(r'%s', 'wb') +while 1: + line = sys.stdin.readline() + if not line: + break + logfp.write('script_recv: ' + line) + sys.stdout.write('script_recv: STDOUT: ' + line) + sys.stderr.write('script_recv: STDERR: ' + line) +logfp.close() + """ % t.recv_out_path + t.run_env.write(t.recv_script_path, text) + os.chmod(t.recv_script_path, 0o644) # XXX UNIX-specific + return t + + def test_start(self): + """Test start()""" + + t = self.setup_run_scripts() + + # Everything before this prepared our "source directory." + # Now do the real test. + try: + test = TestCmd.TestCmd(program = t.script, + interpreter = 'python', + workdir = '', + subdir = 'script_subdir') + + p = test.start() + self.run_match(p.stdout.read(), t.script, "STDOUT", t.workdir, + repr([])) + self.run_match(p.stderr.read(), t.script, "STDERR", t.workdir, + repr([])) + p.wait() + + p = test.start(arguments='arg1 arg2 arg3') + self.run_match(p.stdout.read(), t.script, "STDOUT", t.workdir, + repr(['arg1', 'arg2', 'arg3'])) + self.run_match(p.stderr.read(), t.script, "STDERR", t.workdir, + repr(['arg1', 'arg2', 'arg3'])) + p.wait() + + p = test.start(program=t.scriptx, arguments='foo') + self.run_match(p.stdout.read(), t.scriptx, "STDOUT", t.workdir, + repr(['foo'])) + self.run_match(p.stderr.read(), t.scriptx, "STDERR", t.workdir, + repr(['foo'])) + p.wait() + + p = test.start(program=t.script1, interpreter=['python', '-x']) + self.run_match(p.stdout.read(), t.script1, "STDOUT", t.workdir, + repr([])) + self.run_match(p.stderr.read(), t.script1, "STDERR", t.workdir, + repr([])) + p.wait() + + p = test.start(program='no_script', interpreter='python') + status = p.wait() + assert status != None, status + + try: + p = test.start(program='no_script', interpreter='no_interpreter') + except OSError: + # Python versions that use subprocess throw an OSError + # exception when they try to execute something that + # isn't there. + pass + else: + status = p.wait() + # Python versions that use os.popen3() or the Popen3 + # class run things through the shell, which just returns + # a non-zero exit status. + assert status != None, status + + testx = TestCmd.TestCmd(program = t.scriptx, + workdir = '', + subdir = 't.scriptx_subdir') + + p = testx.start() + self.run_match(p.stdout.read(), t.scriptx, "STDOUT", t.workdir, + repr([])) + self.run_match(p.stderr.read(), t.scriptx, "STDERR", t.workdir, + repr([])) + p.wait() + + p = testx.start(arguments='foo bar') + self.run_match(p.stdout.read(), t.scriptx, "STDOUT", t.workdir, + repr(['foo', 'bar'])) + self.run_match(p.stderr.read(), t.scriptx, "STDERR", t.workdir, + repr(['foo', 'bar'])) + p.wait() + + p = testx.start(program=t.script, interpreter='python', arguments='bar') + self.run_match(p.stdout.read(), t.script, "STDOUT", t.workdir, + repr(['bar'])) + self.run_match(p.stderr.read(), t.script, "STDERR", t.workdir, + repr(['bar'])) + p.wait() + + p = testx.start(program=t.script1, interpreter=('python', '-x')) + self.run_match(p.stdout.read(), t.script1, "STDOUT", t.workdir, + repr([])) + self.run_match(p.stderr.read(), t.script1, "STDERR", t.workdir, + repr([])) + p.wait() + + s = os.path.join('.', t.scriptx) + p = testx.start(program=[s]) + self.run_match(p.stdout.read(), t.scriptx, "STDOUT", t.workdir, + repr([])) + self.run_match(p.stderr.read(), t.scriptx, "STDERR", t.workdir, + repr([])) + p.wait() + + try: + testx.start(program='no_program') + except OSError: + # Python versions that use subprocess throw an OSError + # exception when they try to execute something that + # isn't there. + pass + else: + # Python versions that use os.popen3() or the Popen3 + # class run things through the shell, which just dies + # trying to execute the non-existent program before + # we can wait() for it. + try: + p = p.wait() + except OSError: + pass + + test1 = TestCmd.TestCmd(program = t.script1, + interpreter = ['python', '-x'], + workdir = '') + + p = test1.start() + self.run_match(p.stdout.read(), t.script1, "STDOUT", t.workdir, + repr([])) + self.run_match(p.stderr.read(), t.script1, "STDERR", t.workdir, + repr([])) + p.wait() + + finally: + os.chdir(t.orig_cwd) + + def test_finish(self): + """Test finish()""" + + t = self.setup_run_scripts() + + # Everything before this prepared our "source directory." + # Now do the real test. + try: + + test = TestCmd.TestCmd(program = t.recv_script, + interpreter = 'python', + workdir = '', + subdir = 'script_subdir') + + test.start(stdin=1) + test.finish() + expect_stdout = """\ +script_recv: STDOUT +""" + expect_stderr = """\ +script_recv: STDERR +""" + stdout = test.stdout() + assert stdout == expect_stdout, stdout + stderr = test.stderr() + assert stderr == expect_stderr, stderr + + p = test.start(stdin=1) + p.send('input\n') + test.finish(p) + expect_stdout = """\ +script_recv: STDOUT +script_recv: STDOUT: input +""" + expect_stderr = """\ +script_recv: STDERR +script_recv: STDERR: input +""" + stdout = test.stdout() + assert stdout == expect_stdout, stdout + stderr = test.stderr() + assert stderr == expect_stderr, stderr + + p = test.start(combine=1, stdin=1) + p.send('input\n') + test.finish(p) + expect_stdout = """\ +script_recv: STDOUT +script_recv: STDERR +script_recv: STDOUT: input +script_recv: STDERR: input +""" + expect_stderr = "" + stdout = test.stdout() + assert stdout == expect_stdout, stdout + stderr = test.stderr() + assert stderr == expect_stderr, stderr + + finally: + os.chdir(t.orig_cwd) + + def test_recv(self): + """Test the recv() method of objects returned by start()""" + + t = self.setup_run_scripts() + + # Everything before this prepared our "source directory." + # Now do the real test. + try: + test = TestCmd.TestCmd(program = t.script, + interpreter = 'python', + workdir = '', + subdir = 'script_subdir') + + p = test.start() + stdout = p.recv() + while stdout == '': + import time + time.sleep(1) + stdout = p.recv() + self.run_match(stdout, t.script, "STDOUT", t.workdir, + repr([])) + p.wait() + + finally: + os.chdir(t.orig_cwd) + + def test_recv_err(self): + """Test the recv_err() method of objects returned by start()""" + + t = self.setup_run_scripts() + + # Everything before this prepared our "source directory." + # Now do the real test. + try: + + test = TestCmd.TestCmd(program = t.script, + interpreter = 'python', + workdir = '', + subdir = 'script_subdir') + + p = test.start() + stderr = p.recv_err() + while stderr == '': + import time + time.sleep(1) + stderr = p.recv_err() + self.run_match(stderr, t.script, "STDERR", t.workdir, + repr([])) + p.wait() + + + finally: + os.chdir(t.orig_cwd) + + def test_send(self): + """Test the send() method of objects returned by start()""" + + t = self.setup_run_scripts() + + # Everything before this prepared our "source directory." + # Now do the real test. + try: + + test = TestCmd.TestCmd(program = t.recv_script, + interpreter = 'python', + workdir = '', + subdir = 'script_subdir') + + p = test.start(stdin=1) + input = 'stdin.write() input to the receive script\n' + p.stdin.write(input) + p.stdin.close() + p.wait() + result = open(t.recv_out_path, 'rb').read() + expect = 'script_recv: ' + input + assert result == expect, repr(result) + + p = test.start(stdin=1) + input = 'send() input to the receive script\n' + p.send(input) + p.stdin.close() + p.wait() + result = open(t.recv_out_path, 'rb').read() + expect = 'script_recv: ' + input + assert result == expect, repr(result) + + finally: + os.chdir(t.orig_cwd) + + # TODO(sgk): figure out how to eliminate the race conditions here. + def __FLAKY__test_send_recv(self): + """Test the send_recv() method of objects returned by start()""" + + t = self.setup_run_scripts() + + # Everything before this prepared our "source directory." + # Now do the real test. + try: + + test = TestCmd.TestCmd(program = t.recv_script, + interpreter = 'python', + workdir = '', + subdir = 'script_subdir') + + def do_send_recv(p, input): + send, stdout, stderr = p.send_recv(input) + stdout = self.translate_newlines(stdout) + stderr = self.translate_newlines(stderr) + return send, stdout, stderr + + p = test.start(stdin=1) + input = 'input to the receive script\n' + send, stdout, stderr = do_send_recv(p, input) + # Buffering issues and a race condition prevent this from + # being completely deterministic, so check for both null + # output and the first write() on each stream. + assert stdout in ("", "script_recv: STDOUT\n"), stdout + assert stderr in ("", "script_recv: STDERR\n"), stderr + send, stdout, stderr = do_send_recv(p, input) + assert stdout in ("", "script_recv: STDOUT\n"), stdout + assert stderr in ("", "script_recv: STDERR\n"), stderr + p.stdin.close() + stdout = self.translate_newlines(p.recv()) + stderr = self.translate_newlines(p.recv_err()) + assert stdout in ("", "script_recv: STDOUT\n"), stdout + assert stderr in ("", "script_recv: STDERR\n"), stderr + p.wait() + stdout = self.translate_newlines(p.recv()) + stderr = self.translate_newlines(p.recv_err()) + expect_stdout = """\ +script_recv: STDOUT +script_recv: STDOUT: input to the receive script +script_recv: STDOUT: input to the receive script +""" + expect_stderr = """\ +script_recv: STDERR +script_recv: STDERR: input to the receive script +script_recv: STDERR: input to the receive script +""" + assert stdout == expect_stdout, stdout + assert stderr == expect_stderr, stderr + result = open(t.recv_out_path, 'rb').read() + expect = ('script_recv: ' + input) * 2 + assert result == expect, (result, stdout, stderr) + + finally: + os.chdir(t.orig_cwd) + + + +class stdin_TestCase(TestCmdTestCase): + def test_stdin(self): + """Test stdin()""" + run_env = TestCmd.TestCmd(workdir = '') + run_env.write('run', """from __future__ import print_function +import fileinput +for line in fileinput.input(): + print('Y'.join(line[:-1].split('X'))) +""") + run_env.write('input', "X on X this X line X\n") + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + test = TestCmd.TestCmd(program = 'run', interpreter = 'python', workdir = '') + test.run(arguments = 'input') + assert test.stdout() == "Y on Y this Y line Y\n" + test.run(stdin = "X is X here X tooX\n") + assert test.stdout() == "Y is Y here Y tooY\n" + test.run(stdin = """X here X +X there X +""") + assert test.stdout() == "Y here Y\nY there Y\n" + test.run(stdin = ["X line X\n", "X another X\n"]) + assert test.stdout() == "Y line Y\nY another Y\n" + + + +class stdout_TestCase(TestCmdTestCase): + def test_stdout(self): + """Test stdout()""" + run_env = TestCmd.TestCmd(workdir = '') + run_env.write('run1', """import sys +sys.stdout.write("run1 STDOUT %s\\n" % sys.argv[1:]) +sys.stdout.write("run1 STDOUT second line\\n") +sys.stderr.write("run1 STDERR %s\\n" % sys.argv[1:]) +sys.stderr.write("run1 STDERR second line\\n") +""") + run_env.write('run2', """import sys +sys.stdout.write("run2 STDOUT %s\\n" % sys.argv[1:]) +sys.stdout.write("run2 STDOUT second line\\n") +sys.stderr.write("run2 STDERR %s\\n" % sys.argv[1:]) +sys.stderr.write("run2 STDERR second line\\n") +""") + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + test = TestCmd.TestCmd(interpreter = 'python', workdir = '') + try: + output = test.stdout() + except IndexError: + pass + else: + raise IndexError("got unexpected output:\n\t`%s'\n" % output) + test.program_set('run1') + test.run(arguments = 'foo bar') + test.program_set('run2') + test.run(arguments = 'snafu') + # XXX SHOULD TEST ABSOLUTE NUMBER AS WELL + output = test.stdout() + assert output == "run2 STDOUT ['snafu']\nrun2 STDOUT second line\n", output + output = test.stdout(run = -1) + assert output == "run1 STDOUT ['foo', 'bar']\nrun1 STDOUT second line\n", output + + + +class subdir_TestCase(TestCmdTestCase): + def test_subdir(self): + """Test subdir()""" + test = TestCmd.TestCmd(workdir = '', subdir = ['no', 'such', 'subdir']) + assert not os.path.exists(test.workpath('no')) + + test = TestCmd.TestCmd(workdir = '', subdir = 'foo') + assert test.subdir('bar') == 1 + assert test.subdir(['foo', 'succeed']) == 1 + if os.name != "nt": + os.chmod(test.workpath('foo'), 0o500) + assert test.subdir(['foo', 'fail']) == 0 + assert test.subdir(['sub', 'dir', 'ectory'], 'sub') == 1 + assert test.subdir('one', + UserList(['one', 'two']), + ['one', 'two', 'three']) == 3 + assert os.path.isdir(test.workpath('foo')) + assert os.path.isdir(test.workpath('bar')) + assert os.path.isdir(test.workpath('foo', 'succeed')) + if os.name != "nt": + assert not os.path.exists(test.workpath('foo', 'fail')) + assert os.path.isdir(test.workpath('sub')) + assert not os.path.exists(test.workpath('sub', 'dir')) + assert not os.path.exists(test.workpath('sub', 'dir', 'ectory')) + assert os.path.isdir(test.workpath('one', 'two', 'three')) + + + +class symlink_TestCase(TestCmdTestCase): + def test_symlink(self): + """Test symlink()""" + try: os.symlink + except AttributeError: return + + test = TestCmd.TestCmd(workdir = '', subdir = 'foo') + wdir_file1 = os.path.join(test.workdir, 'file1') + wdir_target1 = os.path.join(test.workdir, 'target1') + wdir_foo_file2 = os.path.join(test.workdir, 'foo', 'file2') + wdir_target2 = os.path.join(test.workdir, 'target2') + wdir_foo_target2 = os.path.join(test.workdir, 'foo', 'target2') + + test.symlink('target1', 'file1') + assert os.path.islink(wdir_file1) + assert not os.path.exists(wdir_file1) + open(wdir_target1, 'w').write("") + assert os.path.exists(wdir_file1) + + test.symlink('target2', ['foo', 'file2']) + assert os.path.islink(wdir_foo_file2) + assert not os.path.exists(wdir_foo_file2) + open(wdir_target2, 'w').write("") + assert not os.path.exists(wdir_foo_file2) + open(wdir_foo_target2, 'w').write("") + assert os.path.exists(wdir_foo_file2) + + + +class tempdir_TestCase(TestCmdTestCase): + def setUp(self): + TestCmdTestCase.setUp(self) + self._tempdir = tempfile.mktemp() + os.mkdir(self._tempdir) + os.chdir(self._tempdir) + + def tearDown(self): + TestCmdTestCase.tearDown(self) + os.rmdir(self._tempdir) + + def test_tempdir(self): + """Test tempdir()""" + test = TestCmd.TestCmd() + tdir1 = test.tempdir() + assert os.path.isdir(tdir1) + test.workdir_set(None) + test.cleanup() + assert not os.path.exists(tdir1) + + test = TestCmd.TestCmd() + tdir2 = test.tempdir('temp') + assert os.path.isdir(tdir2) + tdir3 = test.tempdir() + assert os.path.isdir(tdir3) + test.workdir_set(None) + test.cleanup() + assert not os.path.exists(tdir2) + assert not os.path.exists(tdir3) + + +timeout_script = """\ +import sys +import time +seconds = int(sys.argv[1]) +sys.stdout.write('sleeping %s\\n' % seconds) +sys.stdout.flush() +time.sleep(seconds) +sys.stdout.write('slept %s\\n' % seconds) +sys.stdout.flush() +sys.exit(0) +""" + +class timeout_TestCase(TestCmdTestCase): + def test_initialization(self): + """Test initialization timeout""" + test = TestCmd.TestCmd(workdir='', timeout=2) + test.write('sleep.py', timeout_script) + + test.run([sys.executable, test.workpath('sleep.py'), '4']) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 4\n', test.stdout() + + test.run([sys.executable, test.workpath('sleep.py'), '4']) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 4\n', test.stdout() + + def test_cancellation(self): + """Test timer cancellation after firing""" + test = TestCmd.TestCmd(workdir='', timeout=4) + test.write('sleep.py', timeout_script) + + test.run([sys.executable, test.workpath('sleep.py'), '6']) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 6\n', test.stdout() + + test.run([sys.executable, test.workpath('sleep.py'), '2']) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 2\nslept 2\n', test.stdout() + + test.run([sys.executable, test.workpath('sleep.py'), '6']) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 6\n', test.stdout() + + def test_run(self): + """Test run() timeout""" + test = TestCmd.TestCmd(workdir='', timeout=8) + test.write('sleep.py', timeout_script) + + test.run([sys.executable, test.workpath('sleep.py'), '2'], + timeout=4) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 2\nslept 2\n', test.stdout() + + test.run([sys.executable, test.workpath('sleep.py'), '6'], + timeout=4) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 6\n', test.stdout() + + def test_set_timeout(self): + """Test set_timeout()""" + test = TestCmd.TestCmd(workdir='', timeout=2) + test.write('sleep.py', timeout_script) + + test.run([sys.executable, test.workpath('sleep.py'), '4']) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 4\n', test.stdout() + + test.set_timeout(None) + + test.run([sys.executable, test.workpath('sleep.py'), '4']) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 4\nslept 4\n', test.stdout() + + test.set_timeout(6) + + test.run([sys.executable, test.workpath('sleep.py'), '4']) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 4\nslept 4\n', test.stdout() + + test.run([sys.executable, test.workpath('sleep.py'), '8']) + assert test.stderr() == '', test.stderr() + assert test.stdout() == 'sleeping 8\n', test.stdout() + + + +class unlink_TestCase(TestCmdTestCase): + def test_unlink(self): + """Test unlink()""" + test = TestCmd.TestCmd(workdir = '', subdir = 'foo') + wdir_file1 = os.path.join(test.workdir, 'file1') + wdir_file2 = os.path.join(test.workdir, 'file2') + wdir_foo_file3a = os.path.join(test.workdir, 'foo', 'file3a') + wdir_foo_file3b = os.path.join(test.workdir, 'foo', 'file3b') + wdir_foo_file4 = os.path.join(test.workdir, 'foo', 'file4') + wdir_file5 = os.path.join(test.workdir, 'file5') + + open(wdir_file1, 'w').write("") + open(wdir_file2, 'w').write("") + open(wdir_foo_file3a, 'w').write("") + open(wdir_foo_file3b, 'w').write("") + open(wdir_foo_file4, 'w').write("") + open(wdir_file5, 'w').write("") + + try: + contents = test.unlink('no_file') + except OSError: # expect "No such file or directory" + pass + except: + raise + + test.unlink("file1") + assert not os.path.exists(wdir_file1) + + test.unlink(wdir_file2) + assert not os.path.exists(wdir_file2) + + test.unlink(['foo', 'file3a']) + assert not os.path.exists(wdir_foo_file3a) + + test.unlink(UserList(['foo', 'file3b'])) + assert not os.path.exists(wdir_foo_file3b) + + test.unlink([test.workdir, 'foo', 'file4']) + assert not os.path.exists(wdir_foo_file4) + + # Make it so we can't unlink file5. + # For UNIX, remove write permission from the dir and the file. + # For Windows, open the file. + os.chmod(test.workdir, 0o500) + os.chmod(wdir_file5, 0o400) + f = open(wdir_file5, 'r') + + try: + try: + test.unlink('file5') + except OSError: # expect "Permission denied" + pass + except: + raise + finally: + os.chmod(test.workdir, 0o700) + os.chmod(wdir_file5, 0o600) + f.close() + + + +class touch_TestCase(TestCmdTestCase): + def test_touch(self): + """Test touch()""" + test = TestCmd.TestCmd(workdir = '', subdir = 'sub') + + wdir_file1 = os.path.join(test.workdir, 'file1') + wdir_sub_file2 = os.path.join(test.workdir, 'sub', 'file2') + + open(wdir_file1, 'w').write("") + open(wdir_sub_file2, 'w').write("") + + file1_old_time = os.path.getmtime(wdir_file1) + file2_old_time = os.path.getmtime(wdir_sub_file2) + + test.sleep() + + test.touch(wdir_file1) + + file1_new_time = os.path.getmtime(wdir_file1) + assert file1_new_time > file1_old_time + + test.touch('file1', file1_old_time) + + result = os.path.getmtime(wdir_file1) + # Sub-second granularity of file systems may still vary. + # On Windows, the two times may be off by a microsecond. + assert int(result) == int(file1_old_time), (result, file1_old_time) + + test.touch(['sub', 'file2']) + + file2_new_time = os.path.getmtime(wdir_sub_file2) + assert file2_new_time > file2_old_time + + + +class verbose_TestCase(TestCmdTestCase): + def test_verbose(self): + """Test verbose()""" + test = TestCmd.TestCmd() + assert test.verbose == 0, 'verbose already initialized?' + test = TestCmd.TestCmd(verbose = 1) + assert test.verbose == 1, 'did not initialize verbose' + test.verbose = 2 + assert test.verbose == 2, 'did not set verbose' + + + +class workdir_TestCase(TestCmdTestCase): + def test_workdir(self): + """Test workdir()""" + run_env = TestCmd.TestCmd(workdir = '') + os.chdir(run_env.workdir) + # Everything before this prepared our "source directory." + # Now do the real test. + test = TestCmd.TestCmd() + assert test.workdir is None + + test = TestCmd.TestCmd(workdir = None) + assert test.workdir is None + + test = TestCmd.TestCmd(workdir = '') + assert test.workdir != None + assert os.path.isdir(test.workdir) + + test = TestCmd.TestCmd(workdir = 'dir') + assert test.workdir != None + assert os.path.isdir(test.workdir) + + no_such_subdir = os.path.join('no', 'such', 'subdir') + try: + test = TestCmd.TestCmd(workdir = no_such_subdir) + except OSError: # expect "No such file or directory" + pass + except: + raise + + test = TestCmd.TestCmd(workdir = 'foo') + workdir_foo = test.workdir + assert workdir_foo != None + + test.workdir_set('bar') + workdir_bar = test.workdir + assert workdir_bar != None + + try: + test.workdir_set(no_such_subdir) + except OSError: + pass # expect "No such file or directory" + except: + raise + assert workdir_bar == test.workdir + + assert os.path.isdir(workdir_foo) + assert os.path.isdir(workdir_bar) + + + +class workdirs_TestCase(TestCmdTestCase): + def test_workdirs(self): + """Test workdirs()""" + test = TestCmd.TestCmd() + assert test.workdir is None + test.workdir_set('') + wdir1 = test.workdir + test.workdir_set('') + wdir2 = test.workdir + assert os.path.isdir(wdir1) + assert os.path.isdir(wdir2) + test.cleanup() + assert not os.path.exists(wdir1) + assert not os.path.exists(wdir2) + + + +class workpath_TestCase(TestCmdTestCase): + def test_workpath(self): + """Test workpath()""" + test = TestCmd.TestCmd() + assert test.workdir is None + + test = TestCmd.TestCmd(workdir = '') + wpath = test.workpath('foo', 'bar') + assert wpath == os.path.join(test.workdir, 'foo', 'bar') + + + +class readable_TestCase(TestCmdTestCase): + def test_readable(self): + """Test readable()""" + test = TestCmd.TestCmd(workdir = '', subdir = 'foo') + test.write('file1', "Test file #1\n") + test.write(['foo', 'file2'], "Test file #2\n") + + try: symlink = os.symlink + except AttributeError: pass + else: symlink('no_such_file', test.workpath('dangling_symlink')) + + test.readable(test.workdir, 0) + # XXX skip these tests if euid == 0? + assert not _is_readable(test.workdir) + assert not _is_readable(test.workpath('file1')) + assert not _is_readable(test.workpath('foo')) + assert not _is_readable(test.workpath('foo', 'file2')) + + test.readable(test.workdir, 1) + assert _is_readable(test.workdir) + assert _is_readable(test.workpath('file1')) + assert _is_readable(test.workpath('foo')) + assert _is_readable(test.workpath('foo', 'file2')) + + test.readable(test.workdir, 0) + # XXX skip these tests if euid == 0? + assert not _is_readable(test.workdir) + assert not _is_readable(test.workpath('file1')) + assert not _is_readable(test.workpath('foo')) + assert not _is_readable(test.workpath('foo', 'file2')) + + test.readable(test.workpath('file1'), 1) + assert _is_readable(test.workpath('file1')) + + test.readable(test.workpath('file1'), 0) + assert not _is_readable(test.workpath('file1')) + + test.readable(test.workdir, 1) + + + +class writable_TestCase(TestCmdTestCase): + def test_writable(self): + """Test writable()""" + test = TestCmd.TestCmd(workdir = '', subdir = 'foo') + test.write('file1', "Test file #1\n") + test.write(['foo', 'file2'], "Test file #2\n") + + try: symlink = os.symlink + except AttributeError: pass + else: symlink('no_such_file', test.workpath('dangling_symlink')) + + test.writable(test.workdir, 0) + # XXX skip these tests if euid == 0? + assert not _is_writable(test.workdir) + assert not _is_writable(test.workpath('file1')) + assert not _is_writable(test.workpath('foo')) + assert not _is_writable(test.workpath('foo', 'file2')) + + test.writable(test.workdir, 1) + assert _is_writable(test.workdir) + assert _is_writable(test.workpath('file1')) + assert _is_writable(test.workpath('foo')) + assert _is_writable(test.workpath('foo', 'file2')) + + test.writable(test.workdir, 0) + # XXX skip these tests if euid == 0? + assert not _is_writable(test.workdir) + assert not _is_writable(test.workpath('file1')) + assert not _is_writable(test.workpath('foo')) + assert not _is_writable(test.workpath('foo', 'file2')) + + test.writable(test.workpath('file1'), 1) + assert _is_writable(test.workpath('file1')) + + test.writable(test.workpath('file1'), 0) + assert not _is_writable(test.workpath('file1')) + + + +class executable_TestCase(TestCmdTestCase): + def test_executable(self): + """Test executable()""" + test = TestCmd.TestCmd(workdir = '', subdir = 'foo') + test.write('file1', "Test file #1\n") + test.write(['foo', 'file2'], "Test file #2\n") + + try: symlink = os.symlink + except AttributeError: pass + else: symlink('no_such_file', test.workpath('dangling_symlink')) + + def make_executable(fname): + st = os.stat(fname) + os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0o100)) + + def make_non_executable(fname): + st = os.stat(fname) + os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0o100)) + + test.executable(test.workdir, 0) + # XXX skip these tests if euid == 0? + assert not _is_executable(test.workdir) + make_executable(test.workdir) + assert not _is_executable(test.workpath('file1')) + assert not _is_executable(test.workpath('foo')) + make_executable(test.workpath('foo')) + assert not _is_executable(test.workpath('foo', 'file2')) + make_non_executable(test.workpath('foo')) + make_non_executable(test.workdir) + + test.executable(test.workdir, 1) + assert _is_executable(test.workdir) + assert _is_executable(test.workpath('file1')) + assert _is_executable(test.workpath('foo')) + assert _is_executable(test.workpath('foo', 'file2')) + + test.executable(test.workdir, 0) + # XXX skip these tests if euid == 0? + assert not _is_executable(test.workdir) + make_executable(test.workdir) + assert not _is_executable(test.workpath('file1')) + assert not _is_executable(test.workpath('foo')) + make_executable(test.workpath('foo')) + assert not _is_executable(test.workpath('foo', 'file2')) + + test.executable(test.workpath('file1'), 1) + assert _is_executable(test.workpath('file1')) + + test.executable(test.workpath('file1'), 0) + assert not _is_executable(test.workpath('file1')) + + test.executable(test.workdir, 1) + + + +class write_TestCase(TestCmdTestCase): + def test_write(self): + """Test write()""" + test = TestCmd.TestCmd(workdir = '', subdir = 'foo') + test.write('file1', "Test file #1\n") + test.write(['foo', 'file2'], "Test file #2\n") + try: + test.write(['bar', 'file3'], "Test file #3 (should not get created)\n") + except IOError: # expect "No such file or directory" + pass + except: + raise + test.write(test.workpath('file4'), "Test file #4.\n") + test.write(test.workpath('foo', 'file5'), "Test file #5.\n") + try: + test.write(test.workpath('bar', 'file6'), "Test file #6 (should not get created)\n") + except IOError: # expect "No such file or directory" + pass + except: + raise + + try: + test.write('file7', "Test file #8.\n", mode = 'r') + except ValueError: # expect "mode must begin with 'w' + pass + except: + raise + + test.write('file8', "Test file #8.\n", mode = 'w') + test.write('file9', "Test file #9.\r\n", mode = 'wb') + + if os.name != "nt": + os.chmod(test.workdir, 0o500) + try: + test.write('file10', "Test file #10 (should not get created).\n") + except IOError: # expect "Permission denied" + pass + except: + raise + + assert os.path.isdir(test.workpath('foo')) + assert not os.path.exists(test.workpath('bar')) + assert os.path.isfile(test.workpath('file1')) + assert os.path.isfile(test.workpath('foo', 'file2')) + assert not os.path.exists(test.workpath('bar', 'file3')) + assert os.path.isfile(test.workpath('file4')) + assert os.path.isfile(test.workpath('foo', 'file5')) + assert not os.path.exists(test.workpath('bar', 'file6')) + assert not os.path.exists(test.workpath('file7')) + assert os.path.isfile(test.workpath('file8')) + assert os.path.isfile(test.workpath('file9')) + if os.name != "nt": + assert not os.path.exists(test.workpath('file10')) + + assert open(test.workpath('file8'), 'r').read() == "Test file #8.\n" + assert open(test.workpath('file9'), 'rb').read() == "Test file #9.\r\n" + + + +class variables_TestCase(TestCmdTestCase): + def test_variables(self): + """Test global variables""" + run_env = TestCmd.TestCmd(workdir = '') + + variables = [ + 'fail_test', + 'no_result', + 'pass_test', + 'match_exact', + 'match_re', + 'match_re_dotall', + 'python', + '_python_', + 'TestCmd', + ] + + script = "from __future__ import print_function\n" + \ + "import TestCmd\n" + \ + '\n'.join([ "print(TestCmd.%s\n)" % v for v in variables ]) + run_env.run(program=sys.executable, stdin=script) + stderr = run_env.stderr() + assert stderr == "", stderr + + script = "from __future__ import print_function\n" + \ + "from TestCmd import *\n" + \ + '\n'.join([ "print(%s)" % v for v in variables ]) + run_env.run(program=sys.executable, stdin=script) + stderr = run_env.stderr() + assert stderr == "", stderr + + + +if __name__ == "__main__": + tclasses = [ + __init__TestCase, + basename_TestCase, + cleanup_TestCase, + chmod_TestCase, + combine_TestCase, + command_args_TestCase, + description_TestCase, + diff_TestCase, + diff_stderr_TestCase, + diff_stdout_TestCase, + exit_TestCase, + fail_test_TestCase, + interpreter_TestCase, + match_TestCase, + match_exact_TestCase, + match_re_dotall_TestCase, + match_re_TestCase, + match_stderr_TestCase, + match_stdout_TestCase, + no_result_TestCase, + pass_test_TestCase, + preserve_TestCase, + program_TestCase, + read_TestCase, + rmdir_TestCase, + run_TestCase, + run_verbose_TestCase, + set_diff_function_TestCase, + set_match_function_TestCase, + sleep_TestCase, + start_TestCase, + stderr_TestCase, + stdin_TestCase, + stdout_TestCase, + subdir_TestCase, + symlink_TestCase, + tempdir_TestCase, + timeout_TestCase, + unlink_TestCase, + touch_TestCase, + verbose_TestCase, + workdir_TestCase, + workdirs_TestCase, + workpath_TestCase, + writable_TestCase, + write_TestCase, + variables_TestCase, + ] + if sys.platform != 'win32': + tclasses.extend([ + executable_TestCase, + readable_TestCase, + ]) + suite = unittest.TestSuite() + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests([ tclass(n) for n in names ]) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/testing/framework/TestCommon.py b/testing/framework/TestCommon.py new file mode 100644 index 0000000..47a149b --- /dev/null +++ b/testing/framework/TestCommon.py @@ -0,0 +1,748 @@ +""" +TestCommon.py: a testing framework for commands and scripts + with commonly useful error handling + +The TestCommon module provides a simple, high-level interface for writing +tests of executable commands and scripts, especially commands and scripts +that interact with the file system. All methods throw exceptions and +exit on failure, with useful error messages. This makes a number of +explicit checks unnecessary, making the test scripts themselves simpler +to write and easier to read. + +The TestCommon class is a subclass of the TestCmd class. In essence, +TestCommon is a wrapper that handles common TestCmd error conditions in +useful ways. You can use TestCommon directly, or subclass it for your +program and add additional (or override) methods to tailor it to your +program's specific needs. Alternatively, the TestCommon class serves +as a useful example of how to define your own TestCmd subclass. + +As a subclass of TestCmd, TestCommon provides access to all of the +variables and methods from the TestCmd module. Consequently, you can +use any variable or method documented in the TestCmd module without +having to explicitly import TestCmd. + +A TestCommon environment object is created via the usual invocation: + + import TestCommon + test = TestCommon.TestCommon() + +You can use all of the TestCmd keyword arguments when instantiating a +TestCommon object; see the TestCmd documentation for details. + +Here is an overview of the methods and keyword arguments that are +provided by the TestCommon class: + + test.must_be_writable('file1', ['file2', ...]) + + test.must_contain('file', 'required text\n') + + test.must_contain_all(output, input, ['title', find]) + + test.must_contain_all_lines(output, lines, ['title', find]) + + test.must_contain_any_line(output, lines, ['title', find]) + + test.must_contain_exactly_lines(output, lines, ['title', find]) + + test.must_exist('file1', ['file2', ...]) + + test.must_match('file', "expected contents\n") + + test.must_not_be_writable('file1', ['file2', ...]) + + test.must_not_contain('file', 'banned text\n') + + test.must_not_contain_any_line(output, lines, ['title', find]) + + test.must_not_exist('file1', ['file2', ...]) + + test.run(options = "options to be prepended to arguments", + stdout = "expected standard output from the program", + stderr = "expected error output from the program", + status = expected_status, + match = match_function) + +The TestCommon module also provides the following variables + + TestCommon.python + TestCommon._python_ + TestCommon.exe_suffix + TestCommon.obj_suffix + TestCommon.shobj_prefix + TestCommon.shobj_suffix + TestCommon.lib_prefix + TestCommon.lib_suffix + TestCommon.dll_prefix + TestCommon.dll_suffix + +""" + +# Copyright 2000-2010 Steven Knight +# This module is free software, and you may redistribute it and/or modify +# it under the same terms as Python itself, so long as this copyright message +# and disclaimer are retained in their original form. +# +# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +# THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +# +# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +# AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +from __future__ import print_function + +__author__ = "Steven Knight " +__revision__ = "TestCommon.py 1.3.D001 2010/06/03 12:58:27 knight" +__version__ = "1.3" + +import copy +import os +import stat +import sys +import glob + +try: + from collections import UserList +except ImportError: + # no 'collections' module or no UserList in collections + exec('from UserList import UserList') + +from TestCmd import * +from TestCmd import __all__ + +__all__.extend([ 'TestCommon', + 'exe_suffix', + 'obj_suffix', + 'shobj_prefix', + 'shobj_suffix', + 'lib_prefix', + 'lib_suffix', + 'dll_prefix', + 'dll_suffix', + ]) + +# Variables that describe the prefixes and suffixes on this system. +if sys.platform == 'win32': + exe_suffix = '.exe' + obj_suffix = '.obj' + shobj_suffix = '.obj' + shobj_prefix = '' + lib_prefix = '' + lib_suffix = '.lib' + dll_prefix = '' + dll_suffix = '.dll' +elif sys.platform == 'cygwin': + exe_suffix = '.exe' + obj_suffix = '.o' + shobj_suffix = '.os' + shobj_prefix = '' + lib_prefix = 'lib' + lib_suffix = '.a' + dll_prefix = 'cyg' + dll_suffix = '.dll' +elif sys.platform.find('irix') != -1: + exe_suffix = '' + obj_suffix = '.o' + shobj_suffix = '.o' + shobj_prefix = '' + lib_prefix = 'lib' + lib_suffix = '.a' + dll_prefix = 'lib' + dll_suffix = '.so' +elif sys.platform.find('darwin') != -1: + exe_suffix = '' + obj_suffix = '.o' + shobj_suffix = '.os' + shobj_prefix = '' + lib_prefix = 'lib' + lib_suffix = '.a' + dll_prefix = 'lib' + dll_suffix = '.dylib' +elif sys.platform.find('sunos') != -1: + exe_suffix = '' + obj_suffix = '.o' + shobj_suffix = '.o' + shobj_prefix = 'so_' + lib_prefix = 'lib' + lib_suffix = '.a' + dll_prefix = 'lib' + dll_suffix = '.so' +else: + exe_suffix = '' + obj_suffix = '.o' + shobj_suffix = '.os' + shobj_prefix = '' + lib_prefix = 'lib' + lib_suffix = '.a' + dll_prefix = 'lib' + dll_suffix = '.so' + +def is_List(e): + return isinstance(e, (list, UserList)) + +def is_Tuple(e): + return isinstance(e, tuple) + +def is_Sequence(e): + return (not hasattr(e, "strip") and + hasattr(e, "__getitem__") or + hasattr(e, "__iter__")) + +def is_writable(f): + mode = os.stat(f)[stat.ST_MODE] + return mode & stat.S_IWUSR + +def separate_files(flist): + existing = [] + missing = [] + for f in flist: + if os.path.exists(f): + existing.append(f) + else: + missing.append(f) + return existing, missing + +if os.name == 'posix': + def _failed(self, status = 0): + if self.status is None or status is None: + return None + return _status(self) != status + def _status(self): + return self.status +elif os.name == 'nt': + def _failed(self, status = 0): + return not (self.status is None or status is None) and \ + self.status != status + def _status(self): + return self.status + +class TestCommon(TestCmd): + + # Additional methods from the Perl Test::Cmd::Common module + # that we may wish to add in the future: + # + # $test->subdir('subdir', ...); + # + # $test->copy('src_file', 'dst_file'); + + def __init__(self, **kw): + """Initialize a new TestCommon instance. This involves just + calling the base class initialization, and then changing directory + to the workdir. + """ + TestCmd.__init__(self, **kw) + os.chdir(self.workdir) + + def options_arguments(self, options, arguments): + """Merges the "options" keyword argument with the arguments.""" + if options: + if arguments is None: + return options + if isinstance(options, str): + options = [options] + if isinstance(arguments, str): + arguments = [arguments] + arguments = ' '.join(options + arguments) + return arguments + + def must_be_writable(self, *files): + """Ensures that the specified file(s) exist and are writable. + An individual file can be specified as a list of directory names, + in which case the pathname will be constructed by concatenating + them. Exits FAILED if any of the files does not exist or is + not writable. + """ + files = [is_List(x) and os.path.join(*x) or x for x in files] + existing, missing = separate_files(files) + unwritable = [x for x in existing if not is_writable(x)] + if missing: + print("Missing files: `%s'" % "', `".join(missing)) + if unwritable: + print("Unwritable files: `%s'" % "', `".join(unwritable)) + self.fail_test(missing + unwritable) + + def must_contain(self, file, required, mode = 'rb', find = None): + """Ensures that the specified file contains the required text. + """ + file_contents = self.read(file, mode) + if find is None: + def find(o, l): + try: + return o.index(l) + except ValueError: + return None + contains = find(file_contents, required) + if not contains: + print("File `%s' does not contain required string." % file) + print(self.banner('Required string ')) + print(required) + print(self.banner('%s contents ' % file)) + print(file_contents) + self.fail_test(not contains) + + def must_contain_all(self, output, input, title=None, find=None): + """Ensures that the specified output string (first argument) + contains all of the specified input as a block (second argument). + + An optional third argument can be used to describe the type + of output being searched, and only shows up in failure output. + + An optional fourth argument can be used to supply a different + function, of the form "find(line, output), to use when searching + for lines in the output. + """ + if find is None: + def find(o, i): + try: + return o.index(i) + except ValueError: + return None + + if is_List(output): + output = os.newline.join(output) + + if find(output, input) is None: + if title is None: + title = 'output' + print('Missing expected input from {}:'.format(title)) + print(input) + print(self.banner(title + ' ')) + print(output) + self.fail_test() + + def must_contain_all_lines(self, output, lines, title=None, find=None): + """Ensures that the specified output string (first argument) + contains all of the specified lines (second argument). + + An optional third argument can be used to describe the type + of output being searched, and only shows up in failure output. + + An optional fourth argument can be used to supply a different + function, of the form "find(line, output), to use when searching + for lines in the output. + """ + if find is None: + def find(o, l): + try: + return o.index(l) + except ValueError: + return None + missing = [] + if is_List(output): + output = '\n'.join(output) + + for line in lines: + if find(output, line) is None: + missing.append(line) + + if missing: + if title is None: + title = 'output' + sys.stdout.write("Missing expected lines from %s:\n" % title) + for line in missing: + sys.stdout.write(' ' + repr(line) + '\n') + sys.stdout.write(self.banner(title + ' ') + '\n') + sys.stdout.write(output) + self.fail_test() + + def must_contain_any_line(self, output, lines, title=None, find=None): + """Ensures that the specified output string (first argument) + contains at least one of the specified lines (second argument). + + An optional third argument can be used to describe the type + of output being searched, and only shows up in failure output. + + An optional fourth argument can be used to supply a different + function, of the form "find(line, output), to use when searching + for lines in the output. + """ + if find is None: + def find(o, l): + try: + return o.index(l) + except ValueError: + return None + for line in lines: + if find(output, line) is not None: + return + + if title is None: + title = 'output' + sys.stdout.write("Missing any expected line from %s:\n" % title) + for line in lines: + sys.stdout.write(' ' + repr(line) + '\n') + sys.stdout.write(self.banner(title + ' ') + '\n') + sys.stdout.write(output) + self.fail_test() + + def must_contain_exactly_lines(self, output, expect, title=None, find=None): + """Ensures that the specified output string (first argument) + contains all of the lines in the expected string (second argument) + with none left over. + + An optional third argument can be used to describe the type + of output being searched, and only shows up in failure output. + + An optional fourth argument can be used to supply a different + function, of the form "find(line, output), to use when searching + for lines in the output. The function must return the index + of the found line in the output, or None if the line is not found. + """ + out = output.splitlines() + if is_List(expect): + exp = [ e.rstrip('\n') for e in expect ] + else: + exp = expect.splitlines() + if sorted(out) == sorted(exp): + # early out for exact match + return + if find is None: + def find(o, l): + try: + return o.index(l) + except ValueError: + return None + missing = [] + for line in exp: + found = find(out, line) + if found is None: + missing.append(line) + else: + out.pop(found) + + if not missing and not out: + # all lines were matched + return + + if title is None: + title = 'output' + if missing: + sys.stdout.write("Missing expected lines from %s:\n" % title) + for line in missing: + sys.stdout.write(' ' + repr(line) + '\n') + sys.stdout.write(self.banner('Missing %s ' % title) + '\n') + if out: + sys.stdout.write("Extra unexpected lines from %s:\n" % title) + for line in out: + sys.stdout.write(' ' + repr(line) + '\n') + sys.stdout.write(self.banner('Extra %s ' % title) + '\n') + sys.stdout.flush() + self.fail_test() + + def must_contain_lines(self, lines, output, title=None, find = None): + # Deprecated; retain for backwards compatibility. + return self.must_contain_all_lines(output, lines, title, find) + + def must_exist(self, *files): + """Ensures that the specified file(s) must exist. An individual + file be specified as a list of directory names, in which case the + pathname will be constructed by concatenating them. Exits FAILED + if any of the files does not exist. + """ + files = [is_List(x) and os.path.join(*x) or x for x in files] + missing = [x for x in files if not os.path.exists(x) and not os.path.islink(x) ] + if missing: + print("Missing files: `%s'" % "', `".join(missing)) + self.fail_test(missing) + + def must_exist_one_of(self, files): + """Ensures that at least one of the specified file(s) exists. + The filenames can be given as a list, where each entry may be + a single path string, or a tuple of folder names and the final + filename that get concatenated. + Supports wildcard names like 'foo-1.2.3-*.rpm'. + Exits FAILED if none of the files exists. + """ + missing = [] + for x in files: + if is_List(x) or is_Tuple(x): + xpath = os.path.join(*x) + else: + xpath = is_Sequence(x) and os.path.join(x) or x + if glob.glob(xpath): + return + missing.append(xpath) + print("Missing one of: `%s'" % "', `".join(missing)) + self.fail_test(missing) + + def must_match(self, file, expect, mode = 'rb', match=None, message=None, newline=None): + """Matches the contents of the specified file (first argument) + against the expected contents (second argument). The expected + contents are a list of lines or a string which will be split + on newlines. + """ + file_contents = self.read(file, mode, newline) + if not match: + match = self.match + try: + self.fail_test(not match(to_str(file_contents), to_str(expect)), message=message) + except KeyboardInterrupt: + raise + except: + print("Unexpected contents of `%s'" % file) + self.diff(expect, file_contents, 'contents ') + raise + + def must_not_contain(self, file, banned, mode = 'rb', find = None): + """Ensures that the specified file doesn't contain the banned text. + """ + file_contents = self.read(file, mode) + if find is None: + def find(o, l): + try: + return o.index(l) + except ValueError: + return None + contains = find(file_contents, banned) + if contains: + print("File `%s' contains banned string." % file) + print(self.banner('Banned string ')) + print(banned) + print(self.banner('%s contents ' % file)) + print(file_contents) + self.fail_test(contains) + + def must_not_contain_any_line(self, output, lines, title=None, find=None): + """Ensures that the specified output string (first argument) + does not contain any of the specified lines (second argument). + + An optional third argument can be used to describe the type + of output being searched, and only shows up in failure output. + + An optional fourth argument can be used to supply a different + function, of the form "find(line, output), to use when searching + for lines in the output. + """ + if find is None: + def find(o, l): + try: + return o.index(l) + except ValueError: + return None + unexpected = [] + for line in lines: + if find(output, line) is not None: + unexpected.append(line) + + if unexpected: + if title is None: + title = 'output' + sys.stdout.write("Unexpected lines in %s:\n" % title) + for line in unexpected: + sys.stdout.write(' ' + repr(line) + '\n') + sys.stdout.write(self.banner(title + ' ') + '\n') + sys.stdout.write(output) + self.fail_test() + + def must_not_contain_lines(self, lines, output, title=None, find=None): + return self.must_not_contain_any_line(output, lines, title, find) + + def must_not_exist(self, *files): + """Ensures that the specified file(s) must not exist. + An individual file be specified as a list of directory names, in + which case the pathname will be constructed by concatenating them. + Exits FAILED if any of the files exists. + """ + files = [is_List(x) and os.path.join(*x) or x for x in files] + existing = [x for x in files if os.path.exists(x) or os.path.islink(x)] + if existing: + print("Unexpected files exist: `%s'" % "', `".join(existing)) + self.fail_test(existing) + + def must_not_exist_any_of(self, files): + """Ensures that none of the specified file(s) exists. + The filenames can be given as a list, where each entry may be + a single path string, or a tuple of folder names and the final + filename that get concatenated. + Supports wildcard names like 'foo-1.2.3-*.rpm'. + Exits FAILED if any of the files exists. + """ + existing = [] + for x in files: + if is_List(x) or is_Tuple(x): + xpath = os.path.join(*x) + else: + xpath = is_Sequence(x) and os.path.join(x) or x + if glob.glob(xpath): + existing.append(xpath) + if existing: + print("Unexpected files exist: `%s'" % "', `".join(existing)) + self.fail_test(existing) + + def must_not_be_writable(self, *files): + """Ensures that the specified file(s) exist and are not writable. + An individual file can be specified as a list of directory names, + in which case the pathname will be constructed by concatenating + them. Exits FAILED if any of the files does not exist or is + writable. + """ + files = [is_List(x) and os.path.join(*x) or x for x in files] + existing, missing = separate_files(files) + writable = [file for file in existing if is_writable(file)] + if missing: + print("Missing files: `%s'" % "', `".join(missing)) + if writable: + print("Writable files: `%s'" % "', `".join(writable)) + self.fail_test(missing + writable) + + def _complete(self, actual_stdout, expected_stdout, + actual_stderr, expected_stderr, status, match): + """ + Post-processes running a subcommand, checking for failure + status and displaying output appropriately. + """ + if _failed(self, status): + expect = '' + if status != 0: + expect = " (expected %s)" % str(status) + print("%s returned %s%s" % (self.program, _status(self), expect)) + print(self.banner('STDOUT ')) + print(actual_stdout) + print(self.banner('STDERR ')) + print(actual_stderr) + self.fail_test() + if (expected_stdout is not None + and not match(actual_stdout, expected_stdout)): + self.diff(expected_stdout, actual_stdout, 'STDOUT ') + if actual_stderr: + print(self.banner('STDERR ')) + print(actual_stderr) + self.fail_test() + if (expected_stderr is not None + and not match(actual_stderr, expected_stderr)): + print(self.banner('STDOUT ')) + print(actual_stdout) + self.diff(expected_stderr, actual_stderr, 'STDERR ') + self.fail_test() + + def start(self, program = None, + interpreter = None, + options = None, + arguments = None, + universal_newlines = None, + **kw): + """ + Starts a program or script for the test environment, handling + any exceptions. + """ + arguments = self.options_arguments(options, arguments) + try: + return TestCmd.start(self, program, interpreter, arguments, + universal_newlines, **kw) + except KeyboardInterrupt: + raise + except Exception as e: + print(self.banner('STDOUT ')) + try: + print(self.stdout()) + except IndexError: + pass + print(self.banner('STDERR ')) + try: + print(self.stderr()) + except IndexError: + pass + cmd_args = self.command_args(program, interpreter, arguments) + sys.stderr.write('Exception trying to execute: %s\n' % cmd_args) + raise e + + def finish(self, popen, stdout = None, stderr = '', status = 0, **kw): + """ + Finishes and waits for the process being run under control of + the specified popen argument. Additional arguments are similar + to those of the run() method: + + stdout The expected standard output from + the command. A value of None means + don't test standard output. + + stderr The expected error output from + the command. A value of None means + don't test error output. + + status The expected exit status from the + command. A value of None means don't + test exit status. + """ + TestCmd.finish(self, popen, **kw) + match = kw.get('match', self.match) + self._complete(self.stdout(), stdout, + self.stderr(), stderr, status, match) + + def run(self, options = None, arguments = None, + stdout = None, stderr = '', status = 0, **kw): + """Runs the program under test, checking that the test succeeded. + + The parameters are the same as the base TestCmd.run() method, + with the addition of: + + options Extra options that get prepended to the beginning + of the arguments. + + stdout The expected standard output from + the command. A value of None means + don't test standard output. + + stderr The expected error output from + the command. A value of None means + don't test error output. + + status The expected exit status from the + command. A value of None means don't + test exit status. + + By default, this expects a successful exit (status = 0), does + not test standard output (stdout = None), and expects that error + output is empty (stderr = ""). + """ + kw['arguments'] = self.options_arguments(options, arguments) + try: + match = kw['match'] + del kw['match'] + except KeyError: + match = self.match + TestCmd.run(self, **kw) + self._complete(self.stdout(), stdout, + self.stderr(), stderr, status, match) + + def skip_test(self, message="Skipping test.\n"): + """Skips a test. + + Proper test-skipping behavior is dependent on the external + TESTCOMMON_PASS_SKIPS environment variable. If set, we treat + the skip as a PASS (exit 0), and otherwise treat it as NO RESULT. + In either case, we print the specified message as an indication + that the substance of the test was skipped. + + (This was originally added to support development under Aegis. + Technically, skipping a test is a NO RESULT, but Aegis would + treat that as a test failure and prevent the change from going to + the next step. Since we ddn't want to force anyone using Aegis + to have to install absolutely every tool used by the tests, we + would actually report to Aegis that a skipped test has PASSED + so that the workflow isn't held up.) + """ + if message: + sys.stdout.write(message) + sys.stdout.flush() + pass_skips = os.environ.get('TESTCOMMON_PASS_SKIPS') + if pass_skips in [None, 0, '0']: + # skip=1 means skip this function when showing where this + # result came from. They only care about the line where the + # script called test.skip_test(), not the line number where + # we call test.no_result(). + self.no_result(skip=1) + else: + # We're under the development directory for this change, + # so this is an Aegis invocation; pass the test (exit 0). + self.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/testing/framework/TestCommonTests.py b/testing/framework/TestCommonTests.py new file mode 100644 index 0000000..7949cb8 --- /dev/null +++ b/testing/framework/TestCommonTests.py @@ -0,0 +1,2340 @@ +#!/usr/bin/env python +""" +TestCommonTests.py: Unit tests for the TestCommon.py module. + +Copyright 2000-2010 Steven Knight +This module is free software, and you may redistribute it and/or modify +it under the same terms as Python itself, so long as this copyright message +and disclaimer are retained in their original form. + +IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +""" + +__author__ = "Steven Knight " +__revision__ = "TestCommonTests.py 1.3.D001 2010/06/03 12:58:27 knight" + +import difflib +import os +import re +import signal +import stat +import sys +import unittest + +# Strip the current directory so we get the right TestCommon.py module. +sys.path = sys.path[1:] + +import TestCmd +import TestCommon + +def lstrip(s): + lines = [ _.expandtabs() for _ in s.split('\n') ] + if lines[0] == '': + lines = lines[1:] + spaces = len(re.match('^( *).*', lines[0]).group(1)) + if spaces: + lines = [ l[spaces:] for l in lines ] + return '\n'.join(lines) + +if sys.version[:3] == '1.5': + expected_newline = '\\012' +else: + expected_newline = '\\n' + +def assert_display(expect, result, error=None): + try: + expect = expect.pattern + except AttributeError: + pass + result = [ + '\n', + ('*'*80) + '\n', + expect, + ('*'*80) + '\n', + result, + ('*'*80) + '\n', + ] + if error: + result.append(error) + return ''.join(result) + + +class TestCommonTestCase(unittest.TestCase): + """Base class for TestCommon test cases, fixture and utility methods.""" + create_run_env = True + + def setUp(self): + self.orig_cwd = os.getcwd() + if self.create_run_env: + self.run_env = TestCmd.TestCmd(workdir = '') + + def tearDown(self): + os.chdir(self.orig_cwd) + + def set_up_execution_scripts(self): + run_env = self.run_env + + run_env.subdir('sub dir') + + self.python = sys.executable + + self.pass_script = run_env.workpath('sub dir', 'pass') + self.fail_script = run_env.workpath('sub dir', 'fail') + self.stdout_script = run_env.workpath('sub dir', 'stdout') + self.stderr_script = run_env.workpath('sub dir', 'stderr') + self.signal_script = run_env.workpath('sub dir', 'signal') + self.stdin_script = run_env.workpath('sub dir', 'stdin') + + preamble = "import sys" + stdout = "; sys.stdout.write(r'%s: STDOUT: ' + repr(sys.argv[1:]) + '\\n')" + stderr = "; sys.stderr.write(r'%s: STDERR: ' + repr(sys.argv[1:]) + '\\n')" + exit0 = "; sys.exit(0)" + exit1 = "; sys.exit(1)" + if sys.platform == 'win32': + wrapper = '@python -c "%s" %%1 %%2 %%3 %%4 %%5 %%6 %%7 %%8 %%9\n' + else: + wrapper = '#! /usr/bin/env python\n%s\n' + wrapper = '#! /usr/bin/env python\n%s\n' + + pass_body = preamble + stdout % self.pass_script + exit0 + fail_body = preamble + stdout % self.fail_script + exit1 + stderr_body = preamble + stderr % self.stderr_script + exit0 + + run_env.write(self.pass_script, wrapper % pass_body) + run_env.write(self.fail_script, wrapper % fail_body) + run_env.write(self.stderr_script, wrapper % stderr_body) + + signal_body = lstrip("""\ + import os + import signal + os.kill(os.getpid(), signal.SIGTERM) + """) + + run_env.write(self.signal_script, wrapper % signal_body) + + stdin_body = lstrip("""\ + import sys + input = sys.stdin.read()[:-1] + sys.stdout.write(r'%s: STDOUT: ' + repr(input) + '\\n') + sys.stderr.write(r'%s: STDERR: ' + repr(input) + '\\n') + """ % (self.stdin_script, self.stdin_script)) + + run_env.write(self.stdin_script, wrapper % stdin_body) + + def run_execution_test(self, script, expect_stdout, expect_stderr): + self.set_up_execution_scripts() + + run_env = self.run_env + + os.chdir(run_env.workpath('sub dir')) + + # Everything before this prepared our "source directory." + # Now do the real test. + script = script % self.__dict__ + run_env.run(program=sys.executable, stdin=script) + + stdout = run_env.stdout() + stderr = run_env.stderr() + + expect_stdout = expect_stdout % self.__dict__ + assert stdout == expect_stdout, assert_display(expect_stdout, + stdout, + stderr) + + try: + match = expect_stderr.match + except AttributeError: + expect_stderr = expect_stderr % self.__dict__ + assert stderr == expect_stderr, assert_display(expect_stderr, + stderr) + else: + assert expect_stderr.match(stderr), assert_display(expect_stderr, + stderr) + + +class __init__TestCase(TestCommonTestCase): + def test___init__(self): + """Test initialization""" + run_env = self.run_env + + os.chdir(run_env.workdir) + script = lstrip("""\ + from __future__ import print_function + from TestCommon import TestCommon + tc = TestCommon(workdir='') + import os + print(os.getcwd()) + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout()[:-1] + assert stdout != run_env.workdir, stdout + stderr = run_env.stderr() + assert stderr == "", stderr + + +class banner_TestCase(TestCommonTestCase): + create_run_env = False + def test_banner(self): + """Test banner()""" + tc = TestCommon.TestCommon(workdir='') + + b = tc.banner('xyzzy ') + assert b == "xyzzy ==========================================================================", b + + tc.banner_width = 10 + + b = tc.banner('xyzzy ') + assert b == "xyzzy ====", b + + b = tc.banner('xyzzy ', 20) + assert b == "xyzzy ==============", b + + tc.banner_char = '-' + + b = tc.banner('xyzzy ') + assert b == "xyzzy ----", b + +class must_be_writable_TestCase(TestCommonTestCase): + def test_file_does_not_exists(self): + """Test must_be_writable(): file does not exist""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.must_be_writable('file1') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Missing files: `file1'\n", stdout + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_writable_file_exists(self): + """Test must_be_writable(): writable file exists""" + run_env = self.run_env + + script = lstrip("""\ + import os + import stat + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n") + f1 = tc.workpath('file1') + mode = os.stat(f1)[stat.ST_MODE] + os.chmod(f1, mode | stat.S_IWUSR) + tc.must_be_writable('file1') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_non_writable_file_exists(self): + """Test must_be_writable(): non-writable file exists""" + run_env = self.run_env + + script = lstrip("""\ + import os + import stat + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n") + f1 = tc.workpath('file1') + mode = os.stat(f1)[stat.ST_MODE] + os.chmod(f1, mode & ~stat.S_IWUSR) + tc.must_be_writable('file1') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Unwritable files: `file1'\n", stdout + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_file_specified_as_list(self): + """Test must_be_writable(): file specified as list""" + run_env = self.run_env + + script = lstrip("""\ + import os + import stat + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.subdir('sub') + tc.write(['sub', 'file1'], "sub/file1\\n") + f1 = tc.workpath('sub', 'file1') + mode = os.stat(f1)[stat.ST_MODE] + os.chmod(f1, mode | stat.S_IWUSR) + tc.must_be_writable(['sub', 'file1']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + +class must_contain_TestCase(TestCommonTestCase): + def test_success(self): + """Test must_contain(): success""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1 contents\\n") + tc.must_contain('file1', "1 c") + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_file_missing(self): + """Test must_contain(): file missing""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.must_contain('file1', "1 c\\n") + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr.find("No such file or directory:") != -1, stderr + + def test_failure(self): + """Test must_contain(): failure""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1 does not match\\n") + tc.must_contain('file1', "1 c") + tc.run() + """) + expect = lstrip("""\ + File `file1' does not contain required string. + Required string ================================================================ + 1 c + file1 contents ================================================================= + file1 does not match + + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == expect, repr(stdout) + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_mode(self): + """Test must_contain(): mode""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1 contents\\n", mode='w') + tc.must_contain('file1', "1 c", mode='r') + tc.write('file2', "file2 contents\\n", mode='wb') + tc.must_contain('file2', "2 c", mode='rb') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + + +class must_contain_all_lines_TestCase(TestCommonTestCase): + def test_success(self): + """Test must_contain_all_lines(): success""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + xxx + yyy + zzz + ''' + + test.must_contain_all_lines(output, lines) + + test.must_contain_all_lines(output, ['www\\n']) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_failure(self): + """Test must_contain_all_lines(): failure""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + zzz + ''' + + test.must_contain_all_lines(output, lines) + + test.pass_test() + """) + + expect = lstrip("""\ + Missing expected lines from output: + 'xxx%(expected_newline)s' + 'yyy%(expected_newline)s' + output ========================================================================= + www + zzz + """ % globals()) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + stderr = run_env.stderr() + assert stdout == expect, assert_display(expect, stdout, stderr) + assert stderr.find("FAILED") != -1, stderr + + def test_find(self): + """Test must_contain_all_lines(): find""" + run_env = self.run_env + + script = lstrip(""" + import re + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'x.*', + '.*y', + ] + + output = '''\\ + www + xxx + yyy + zzz + ''' + + def re_search(output, line): + return re.compile(line, re.S).search(output) + test.must_contain_all_lines(output, lines, find=re_search) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_title(self): + """Test must_contain_all_lines(): title""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + zzz + ''' + + test.must_contain_all_lines(output, lines, title='STDERR') + + test.pass_test() + """) + + expect = lstrip("""\ + Missing expected lines from STDERR: + 'xxx%(expected_newline)s' + 'yyy%(expected_newline)s' + STDERR ========================================================================= + www + zzz + """ % globals()) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + stderr = run_env.stderr() + assert stdout == expect, assert_display(expect, stdout, stderr) + assert stderr.find("FAILED") != -1, stderr + + + +class must_contain_any_line_TestCase(TestCommonTestCase): + def test_success(self): + """Test must_contain_any_line(): success""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'aaa\\n', + 'yyy\\n', + ] + + output = '''\\ + www + xxx + yyy + zzz + ''' + + test.must_contain_any_line(output, lines) + + test.must_contain_any_line(output, ['www\\n']) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_failure(self): + """Test must_contain_any_line(): failure""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + zzz + ''' + + test.must_contain_any_line(output, lines) + + test.pass_test() + """) + + expect = lstrip("""\ + Missing any expected line from output: + 'xxx%(expected_newline)s' + 'yyy%(expected_newline)s' + output ========================================================================= + www + zzz + """ % globals()) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + stderr = run_env.stderr() + assert stdout == expect, assert_display(expect, stdout, stderr) + assert stderr.find("FAILED") != -1, stderr + + def test_find(self): + """Test must_contain_any_line(): find""" + run_env = self.run_env + + script = lstrip(""" + import re + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'aaa', + '.*y', + ] + + output = '''\\ + www + xxx + yyy + zzz + ''' + + def re_search(output, line): + return re.compile(line, re.S).search(output) + test.must_contain_any_line(output, lines, find=re_search) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_title(self): + """Test must_contain_any_line(): title""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + zzz + ''' + + test.must_contain_any_line(output, lines, title='STDOUT') + + test.pass_test() + """) + + expect = lstrip("""\ + Missing any expected line from STDOUT: + 'xxx%(expected_newline)s' + 'yyy%(expected_newline)s' + STDOUT ========================================================================= + www + zzz + """ % globals()) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + stderr = run_env.stderr() + assert stdout == expect, assert_display(expect, stdout, stderr) + assert stderr.find("FAILED") != -1, stderr + + + +class must_contain_exactly_lines_TestCase(TestCommonTestCase): + def test_success_list(self): + """Test must_contain_exactly_lines(): success (input list)""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'yyy\\n', + 'xxx\\n', + 'zzz', + 'www\\n', + ] + + output = '''\\ + www + xxx + yyy + zzz + ''' + + test.must_contain_exactly_lines(output, lines) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_success_string(self): + """Test must_contain_exactly_lines(): success (input string)""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = '''\\ + yyy + xxx + zzz + www + ''' + + output = '''\\ + www + xxx + yyy + zzz + ''' + + test.must_contain_exactly_lines(output, lines) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_failure(self): + """Test must_contain_exactly_lines(): failure""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + zzz + ''' + + test.must_contain_exactly_lines(output, lines) + + test.pass_test() + """) + + expect = lstrip("""\ + Missing expected lines from output: + 'xxx' + 'yyy' + Missing output ================================================================= + Extra unexpected lines from output: + 'www' + 'zzz' + Extra output =================================================================== + """ % globals()) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + stderr = run_env.stderr() + assert stdout == expect, assert_display(expect, stdout, stderr) + assert stderr.find("FAILED") != -1, stderr + + def test_find(self): + """Test must_contain_exactly_lines(): find""" + run_env = self.run_env + + script = lstrip(""" + import re + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'zzz', + '.*y', + 'xxx', + 'www', + ] + + output = '''\\\ + www + xxx + yyy + zzz + ''' + + def re_search(output, line): + pattern = re.compile(line, re.S) + index = 0 + for o in output: + if pattern.search(o): + return index + index +=1 + return None + test.must_contain_exactly_lines(output, lines, find=re_search) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_title(self): + """Test must_contain_exactly_lines(): title""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + zzz + ''' + + test.must_contain_exactly_lines(output, lines, title='STDOUT') + + test.pass_test() + """) + + expect = lstrip("""\ + Missing expected lines from STDOUT: + 'xxx' + 'yyy' + Missing STDOUT ================================================================= + Extra unexpected lines from STDOUT: + 'www' + 'zzz' + Extra STDOUT =================================================================== + """ % globals()) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + stderr = run_env.stderr() + assert stdout == expect, assert_display(expect, stdout, stderr) + assert stderr.find("FAILED") != -1, stderr + + + +class must_contain_lines_TestCase(TestCommonTestCase): + def test_success(self): + """Test must_contain_lines(): success""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + xxx + yyy + zzz + ''' + + test.must_contain_lines(lines, output) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_failure(self): + """Test must_contain_lines(): failure""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + zzz + ''' + + test.must_contain_lines(lines, output) + + test.pass_test() + """) + + expect = lstrip("""\ + Missing expected lines from output: + 'xxx%(expected_newline)s' + 'yyy%(expected_newline)s' + output ========================================================================= + www + zzz + """ % globals()) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + stderr = run_env.stderr() + assert stdout == expect, assert_display(expect, stdout, stderr) + assert stderr.find("FAILED") != -1, stderr + + + +class must_exist_TestCase(TestCommonTestCase): + def test_success(self): + """Test must_exist(): success""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n") + tc.must_exist('file1') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_failure(self): + """Test must_exist(): failure""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.must_exist('file1') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Missing files: `file1'\n", stdout + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_file_specified_as_list(self): + """Test must_exist(): file specified as list""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.subdir('sub') + tc.write(['sub', 'file1'], "sub/file1\\n") + tc.must_exist(['sub', 'file1']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_broken_link(self) : + """Test must_exist(): exists but it is a broken link""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.symlink('badtarget', "brokenlink") + tc.must_exist('brokenlink') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + +class must_exist_one_of_TestCase(TestCommonTestCase): + def test_success(self): + """Test must_exist_one_of(): success""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n") + tc.must_exist_one_of(['file1']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_failure(self): + """Test must_exist_one_of(): failure""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.must_exist_one_of(['file1']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Missing one of: `file1'\n", stdout + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_files_specified_as_list(self): + """Test must_exist_one_of(): files specified as list""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n") + tc.must_exist_one_of(['file2', 'file1']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_files_specified_with_wildcards(self): + """Test must_exist_one_of(): files specified with wildcards""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file7', "file7\\n") + tc.must_exist_one_of(['file?']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_file_given_as_list(self): + """Test must_exist_one_of(): file given as list""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.subdir('sub') + tc.write(['sub', 'file1'], "sub/file1\\n") + tc.must_exist_one_of(['file2', + ['sub', 'file1']]) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_file_given_as_sequence(self): + """Test must_exist_one_of(): file given as sequence""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.subdir('sub') + tc.write(['sub', 'file1'], "sub/file1\\n") + tc.must_exist_one_of(['file2', + ('sub', 'file1')]) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + +class must_match_TestCase(TestCommonTestCase): + def test_success(self): + """Test must_match(): success""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n") + tc.must_match('file1', "file1\\n") + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_file_does_not_exists(self): + """Test must_match(): file does not exist""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.must_match('file1', "file1\\n") + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr.find("No such file or directory:") != -1, stderr + + def test_failure(self): + """Test must_match(): failure""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1 does not match\\n") + tc.must_match('file1', "file1\\n") + tc.run() + """) + + expect = lstrip("""\ + Unexpected contents of `file1' + contents ======================================================================= + 1c1 + < file1 + --- + > file1 does not match + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == expect, stdout + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_mode(self): + """Test must_match(): mode""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n", mode='w') + tc.must_match('file1', "file1\\n", mode='r') + tc.write('file2', "file2\\n", mode='wb') + tc.must_match('file2', "file2\\n", mode='rb') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + + +class must_not_be_writable_TestCase(TestCommonTestCase): + def test_file_does_not_exists(self): + """Test must_not_be_writable(): file does not exist""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.must_not_be_writable('file1') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Missing files: `file1'\n", stdout + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_writable_file_exists(self): + """Test must_not_be_writable(): writable file exists""" + run_env = self.run_env + + script = lstrip("""\ + import os + import stat + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n") + f1 = tc.workpath('file1') + mode = os.stat(f1)[stat.ST_MODE] + os.chmod(f1, mode | stat.S_IWUSR) + tc.must_not_be_writable('file1') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Writable files: `file1'\n", stdout + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_non_writable_file_exists(self): + """Test must_not_be_writable(): non-writable file exists""" + run_env = self.run_env + + script = lstrip("""\ + import os + import stat + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n") + f1 = tc.workpath('file1') + mode = os.stat(f1)[stat.ST_MODE] + os.chmod(f1, mode & ~stat.S_IWUSR) + tc.must_not_be_writable('file1') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_file_specified_as_list(self): + """Test must_not_be_writable(): file specified as list""" + run_env = self.run_env + + script = lstrip("""\ + import os + import stat + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.subdir('sub') + tc.write(['sub', 'file1'], "sub/file1\\n") + f1 = tc.workpath('sub', 'file1') + mode = os.stat(f1)[stat.ST_MODE] + os.chmod(f1, mode & ~stat.S_IWUSR) + tc.must_not_be_writable(['sub', 'file1']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + + +class must_not_contain_TestCase(TestCommonTestCase): + def test_success(self): + """Test must_not_contain(): success""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1 contents\\n") + tc.must_not_contain('file1', "1 does not contain c") + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_file_does_not_exist(self): + """Test must_not_contain(): file does not exist""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.must_not_contain('file1', "1 c\\n") + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr.find("No such file or directory:") != -1, stderr + + def test_failure(self): + """Test must_not_contain(): failure""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1 does contain contents\\n") + tc.must_not_contain('file1', "1 does contain c") + tc.run() + """) + expect = lstrip("""\ + File `file1' contains banned string. + Banned string ================================================================== + 1 does contain c + file1 contents ================================================================= + file1 does contain contents + + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == expect, repr(stdout) + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_mode(self): + """Test must_not_contain(): mode""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1 contents\\n", mode='w') + tc.must_not_contain('file1', "1 does not contain c", mode='r') + tc.write('file2', "file2 contents\\n", mode='wb') + tc.must_not_contain('file2', "2 does not contain c", mode='rb') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + + +class must_not_contain_any_line_TestCase(TestCommonTestCase): + def test_failure(self): + """Test must_not_contain_any_line(): failure""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + 'www\\n', + ] + + output = '''\\ + www + xxx + yyy + zzz + ''' + + test.must_not_contain_any_line(output, lines) + + test.pass_test() + """) + + expect = lstrip("""\ + Unexpected lines in output: + 'xxx%(expected_newline)s' + 'yyy%(expected_newline)s' + 'www%(expected_newline)s' + output ========================================================================= + www + xxx + yyy + zzz + """ % globals()) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + stderr = run_env.stderr() + assert stdout == expect, assert_display(expect, stdout, stderr) + assert stderr.find("FAILED") != -1, stderr + + def test_find(self): + """Test must_not_contain_any_line(): find""" + run_env = self.run_env + + script = lstrip(""" + import re + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'x.*' + '.*y' + ] + + output = '''\\ + www + zzz + ''' + + def re_search(output, line): + return re.compile(line, re.S).search(output) + test.must_not_contain_any_line(output, lines, find=re_search) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_success(self): + """Test must_not_contain_any_line(): success""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n' + 'yyy\\n' + ] + + output = '''\\ + www + zzz + ''' + + test.must_not_contain_any_line(output, lines) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_title(self): + """Test must_not_contain_any_line(): title""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + xxx + yyy + zzz + ''' + + test.must_not_contain_any_line(output, lines, title='XYZZY') + + test.pass_test() + """) + + expect = lstrip("""\ + Unexpected lines in XYZZY: + 'xxx%(expected_newline)s' + 'yyy%(expected_newline)s' + XYZZY ========================================================================== + www + xxx + yyy + zzz + """ % globals()) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + stderr = run_env.stderr() + assert stdout == expect, assert_display(expect, stdout, stderr) + assert stderr.find("FAILED") != -1, stderr + + + +class must_not_contain_lines_TestCase(TestCommonTestCase): + def test_failure(self): + """Test must_not_contain_lines(): failure""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n', + 'yyy\\n', + ] + + output = '''\\ + www + xxx + yyy + zzz + ''' + + test.must_not_contain_lines(lines, output) + + test.pass_test() + """) + + expect = lstrip("""\ + Unexpected lines in output: + 'xxx%(expected_newline)s' + 'yyy%(expected_newline)s' + output ========================================================================= + www + xxx + yyy + zzz + """ % globals()) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + stderr = run_env.stderr() + assert stdout == expect, assert_display(expect, stdout, stderr) + assert stderr.find("FAILED") != -1, stderr + + def test_success(self): + """Test must_not_contain_lines(): success""" + run_env = self.run_env + + script = lstrip(""" + import TestCommon + test = TestCommon.TestCommon(workdir='') + + lines = [ + 'xxx\\n' + 'yyy\\n' + ] + + output = '''\\ + www + zzz + ''' + + test.must_not_contain_lines(lines, output) + + test.pass_test() + """) + + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + + +class must_not_exist_TestCase(TestCommonTestCase): + def test_failure(self): + """Test must_not_exist(): failure""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n") + tc.must_not_exist('file1') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Unexpected files exist: `file1'\n", stdout + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_success(self): + """Test must_not_exist(): success""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.must_not_exist('file1') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_file_specified_as_list(self): + """Test must_not_exist(): file specified as list""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.subdir('sub') + tc.must_not_exist(['sub', 'file1']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_existing_broken_link(self): + """Test must_not_exist(): exists but it is a broken link""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.symlink('badtarget', 'brokenlink') + tc.must_not_exist('brokenlink') + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Unexpected files exist: `brokenlink'\n", stdout + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + +class must_not_exist_any_of_TestCase(TestCommonTestCase): + def test_success(self): + """Test must_not_exist_any_of(): success""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.must_not_exist_any_of(['file1']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_failure(self): + """Test must_not_exist_any_of(): failure""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file1', "file1\\n") + tc.must_not_exist_any_of(['file1']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Unexpected files exist: `file1'\n", stdout + stderr = run_env.stderr() + assert stderr.find("FAILED") != -1, stderr + + def test_files_specified_as_list(self): + """Test must_not_exist_any_of(): files specified as list""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.must_not_exist_any_of(['file2', 'file1']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_files_specified_with_wildcards(self): + """Test must_not_exist_any_of(): files specified with wildcards""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.write('file7', "file7\\n") + tc.must_not_exist_any_of(['files?']) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_file_given_as_list(self): + """Test must_not_exist_any_of(): file given as list""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.subdir('sub') + tc.write(['sub', 'file1'], "sub/file1\\n") + tc.must_not_exist_any_of(['file2', + ['sub', 'files*']]) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + def test_file_given_as_sequence(self): + """Test must_not_exist_any_of(): file given as sequence""" + run_env = self.run_env + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(workdir='') + tc.subdir('sub') + tc.write(['sub', 'file1'], "sub/file1\\n") + tc.must_not_exist_any_of(['file2', + ('sub', 'files?')]) + tc.pass_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + +class run_TestCase(TestCommonTestCase): + def test_argument_handling(self): + """Test run(): argument handling""" + + script = lstrip("""\ + from TestCommon import TestCommon, match_exact + tc = TestCommon(program=r'%(pass_script)s', + interpreter='%(python)s', + workdir="", + match=match_exact) + tc.run(arguments = "arg1 arg2 arg3", + stdout = r"%(pass_script)s: STDOUT: ['arg1', 'arg2', 'arg3']" + "\\n") + """) + + self.run_execution_test(script, "", "") + + def test_default_pass(self): + """Test run(): default arguments, script passes""" + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(program=r'%(pass_script)s', + interpreter=r'%(python)s', + workdir='') + tc.run() + """) + + self.run_execution_test(script, "", "") + + def test_default_fail(self): + """Test run(): default arguments, script fails""" + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(program=r'%(fail_script)s', + interpreter='%(python)s', + workdir='') + tc.run() + """) + + expect_stdout = lstrip("""\ + %(fail_script)s returned 1 + STDOUT ========================================================================= + %(fail_script)s: STDOUT: [] + + STDERR ========================================================================= + + """) + + expect_stderr = lstrip("""\ + FAILED test of .*fail + \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) + \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) + \\tfrom line \\d+ of ( \(\))? + """) + expect_stderr = re.compile(expect_stderr, re.M) + + self.run_execution_test(script, expect_stdout, expect_stderr) + + def test_default_stderr(self): + """Test run(): default arguments, error output""" + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(program=r'%(stderr_script)s', + interpreter='%(python)s', + workdir='') + tc.run() + """) + + expect_stdout = lstrip("""\ + STDOUT ========================================================================= + + STDERR ========================================================================= + 0a1 + > %(stderr_script)s: STDERR: [] + """) + + expect_stderr = lstrip("""\ + FAILED test of .*stderr + \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) + \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) + \\tfrom line \\d+ of + """) + expect_stderr = re.compile(expect_stderr, re.M) + + self.run_execution_test(script, expect_stdout, expect_stderr) + + def test_exception_handling(self): + """Test run(): exception handling""" + script = lstrip("""\ + import TestCmd + from TestCommon import TestCommon + def raise_exception(*args, **kw): + raise TypeError("forced TypeError") + TestCmd.TestCmd.start = raise_exception + tc = TestCommon(program='%(pass_script)s', + interpreter='%(python)s', + workdir='') + tc.run() + """) + + expect_stdout = lstrip("""\ + STDOUT ========================================================================= + STDERR ========================================================================= + """) + + expect_stderr = lstrip("""\ + Exception trying to execute: \\[%s, '[^']*pass'\\] + Traceback \\((innermost|most recent call) last\\): + File "", line \\d+, in (\\?|) + File "[^"]+TestCommon.py", line \\d+, in run + TestCmd.run\\(self, \\*\\*kw\\) + File "[^"]+TestCmd.py", line \\d+, in run + .* + File "[^"]+TestCommon.py", line \\d+, in start + raise e + TypeError: forced TypeError + """ % re.escape(repr(sys.executable))) + expect_stderr = re.compile(expect_stderr, re.M) + + self.run_execution_test(script, expect_stdout, expect_stderr) + + def test_ignore_stderr(self): + """Test run(): ignore stderr""" + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(program=r'%(stderr_script)s', + interpreter='%(python)s', + workdir='') + tc.run(stderr = None) + """) + + self.run_execution_test(script, "", "") + + def test_match_function_stdout(self): + """Test run(): explicit match function, stdout""" + + script = lstrip("""\ + def my_match_exact(actual, expect): return actual == expect + from TestCommon import TestCommon, match_re_dotall + tc = TestCommon(program=r'%(pass_script)s', + interpreter='%(python)s', + workdir="", + match=match_re_dotall) + tc.run(arguments = "arg1 arg2 arg3", + stdout = r"%(pass_script)s: STDOUT: ['arg1', 'arg2', 'arg3']" + "\\n", + match = my_match_exact) + """) + + self.run_execution_test(script, "", "") + + def test_match_function_stderr(self): + """Test run(): explicit match function, stderr""" + + script = lstrip("""\ + def my_match_exact(actual, expect): return actual == expect + from TestCommon import TestCommon, match_re_dotall + tc = TestCommon(program=r'%(stderr_script)s', + interpreter='%(python)s', + workdir="", + match=match_re_dotall) + tc.run(arguments = "arg1 arg2 arg3", + stderr = r"%(stderr_script)s: STDERR: ['arg1', 'arg2', 'arg3']" + "\\n", + match = my_match_exact) + """) + + self.run_execution_test(script, "", "") + + def test_matched_status_fails(self): + """Test run(): matched status, script fails""" + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(program=r'%(fail_script)s', + interpreter='%(python)s', + workdir='') + tc.run(status = 1) + """) + + self.run_execution_test(script, "", "") + + def test_matched_stdout(self): + """Test run(): matched stdout""" + + script = lstrip("""\ + from TestCommon import TestCommon, match_exact + tc = TestCommon(program=r'%(pass_script)s', + interpreter='%(python)s', + workdir="", + match=match_exact) + tc.run(stdout = r"%(pass_script)s: STDOUT: []" + "\\n") + """) + + self.run_execution_test(script, "", "") + + def test_matched_stderr(self): + """Test run(): matched stderr""" + + script = lstrip("""\ + from TestCommon import TestCommon, match_exact + tc = TestCommon(program=r'%(stderr_script)s', + interpreter='%(python)s', + workdir="", + match=match_exact) + tc.run(stderr = r"%(stderr_script)s: STDERR: []" + "\\n") + """) + + self.run_execution_test(script, "", "") + + def test_mismatched_status_pass(self): + """Test run(): mismatched status, script passes""" + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(program=r'%(pass_script)s', + interpreter='%(python)s', + workdir='') + tc.run(status = 1) + """) + + expect_stdout = lstrip("""\ + %(pass_script)s returned 0 (expected 1) + STDOUT ========================================================================= + %(pass_script)s: STDOUT: [] + + STDERR ========================================================================= + + """) + + expect_stderr = lstrip("""\ + FAILED test of .*pass + \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) + \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) + \\tfrom line \\d+ of ( \(\))? + """) + expect_stderr = re.compile(expect_stderr, re.M) + + self.run_execution_test(script, expect_stdout, expect_stderr) + + def test_mismatched_status_fail(self): + """Test run(): mismatched status, script fails""" + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(program=r'%(fail_script)s', + interpreter='%(python)s', + workdir='') + tc.run(status = 2) + """) + + expect_stdout = lstrip("""\ + %(fail_script)s returned 1 (expected 2) + STDOUT ========================================================================= + %(fail_script)s: STDOUT: [] + + STDERR ========================================================================= + + """) + + expect_stderr = lstrip("""\ + FAILED test of .*fail + \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) + \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) + \\tfrom line \\d+ of ( \(\))? + """) + expect_stderr = re.compile(expect_stderr, re.M) + + self.run_execution_test(script, expect_stdout, expect_stderr) + + def test_mismatched_stdout(self): + """Test run(): mismatched stdout""" + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(program=r'%(pass_script)s', + interpreter='%(python)s', + workdir='') + tc.run(stdout = "Not found\\n") + """) + + expect_stdout = lstrip("""\ + STDOUT ========================================================================= + 1c1 + < Not found + --- + > %(pass_script)s: STDOUT: [] + """) + + expect_stderr = lstrip("""\ + FAILED test of .*pass + \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) + \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) + \\tfrom line \\d+ of ( \(\))? + """) + expect_stderr = re.compile(expect_stderr, re.M) + + self.run_execution_test(script, expect_stdout, expect_stderr) + + def test_mismatched_stderr(self): + """Test run(): mismatched stderr""" + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(program=r'%(stderr_script)s', + interpreter='%(python)s', + workdir='') + tc.run(stderr = "Not found\\n") + """) + + expect_stdout = lstrip("""\ + STDOUT ========================================================================= + + STDERR ========================================================================= + 1c1 + < Not found + --- + > %(stderr_script)s: STDERR: [] + """) + + expect_stderr = lstrip("""\ + FAILED test of .*stderr + \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) + \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) + \\tfrom line \\d+ of ( \(\))? + """) + expect_stderr = re.compile(expect_stderr, re.M) + + self.run_execution_test(script, expect_stdout, expect_stderr) + + def test_option_handling(self): + """Test run(): option handling""" + + script = lstrip("""\ + from TestCommon import TestCommon, match_exact + tc = TestCommon(program=r'%(pass_script)s', + interpreter='%(python)s', + workdir="", + match=match_exact) + tc.run(options = "opt1 opt2 opt3", + stdout = r"%(pass_script)s: STDOUT: ['opt1', 'opt2', 'opt3']" + "\\n") + """) + + self.run_execution_test(script, "", "") + + def test_options_plus_arguments(self): + """Test run(): option handling with arguments""" + + script = lstrip("""\ + from TestCommon import TestCommon, match_exact + tc = TestCommon(program=r'%(pass_script)s', + interpreter='%(python)s', + workdir="", + match=match_exact) + tc.run(options = "opt1 opt2 opt3", + arguments = "arg1 arg2 arg3", + stdout = r"%(pass_script)s: STDOUT: ['opt1', 'opt2', 'opt3', 'arg1', 'arg2', 'arg3']" + "\\n") + """) + + self.run_execution_test(script, "", "") + + def test_signal_handling(self): + """Test run(): signal handling""" + + try: + os.kill + except AttributeError: + sys.stderr.write('can not test, no os.kill ... ') + return + + script = lstrip("""\ + from TestCommon import TestCommon + tc = TestCommon(program=r'%(signal_script)s', + interpreter='%(python)s', + workdir='') + tc.run() + """) + + self.SIGTERM = signal.SIGTERM + + # Script returns the signal value as a negative number. + expect_stdout = lstrip("""\ + %(signal_script)s returned -%(SIGTERM)s + STDOUT ========================================================================= + + STDERR ========================================================================= + + """) + + expect_stderr = lstrip("""\ + FAILED test of .*signal + \\tat line \\d+ of .*TestCommon\\.py \\(_complete\\) + \\tfrom line \\d+ of .*TestCommon\\.py \\(run\\) + \\tfrom line \\d+ of + """) + expect_stderr = re.compile(expect_stderr, re.M) + + self.run_execution_test(script, expect_stdout, expect_stderr) + + def test_stdin(self): + """Test run(): stdin handling""" + + script = lstrip("""\ + from TestCommon import TestCommon, match_exact + tc = TestCommon(program=r'%(stdin_script)s', + interpreter='%(python)s', + workdir='', + match=match_exact) + expect_stdout = r"%(stdin_script)s: STDOUT: 'input'" + "\\n" + expect_stderr = r"%(stdin_script)s: STDERR: 'input'" + "\\n" + tc.run(stdin="input\\n", stdout = expect_stdout, stderr = expect_stderr) + """) + + expect_stdout = lstrip("""\ + %(pass_script)s returned 0 (expected 1) + STDOUT ========================================================================= + %(pass_script)s: STDOUT: [] + + STDERR ========================================================================= + + """) + + self.run_execution_test(script, "", "") + + + +class start_TestCase(TestCommonTestCase): + def test_option_handling(self): + """Test start(): option handling""" + + script = lstrip("""\ + from TestCommon import TestCommon, match_exact + tc = TestCommon(program=r'%(pass_script)s', + interpreter='%(python)s', + workdir="", + match=match_exact) + p = tc.start(options = "opt1 opt2 opt3") + expect = r"%(pass_script)s: STDOUT: ['opt1', 'opt2', 'opt3']" + "\\n" + tc.finish(p, stdout = expect) + """) + + self.run_execution_test(script, "", "") + + def test_options_plus_arguments(self): + """Test start(): option handling with arguments""" + + script = lstrip("""\ + from TestCommon import TestCommon, match_exact + tc = TestCommon(program=r'%(pass_script)s', + interpreter='%(python)s', + workdir="", + match=match_exact) + p = tc.start(options = "opt1 opt2 opt3", + arguments = "arg1 arg2 arg3") + expect = r"%(pass_script)s: STDOUT: ['opt1', 'opt2', 'opt3', 'arg1', 'arg2', 'arg3']" + "\\n" + tc.finish(p, stdout = expect) + """) + + self.run_execution_test(script, "", "") + + + +class skip_test_TestCase(TestCommonTestCase): + def test_skip_test(self): + """Test skip_test()""" + run_env = self.run_env + + script = lstrip("""\ + import TestCommon + test = TestCommon.TestCommon(workdir='') + test.skip_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Skipping test.\n", stdout + stderr = run_env.stderr() + expect = [ + "NO RESULT for test at line 3 of \n", + "NO RESULT for test at line 3 of ()\n", + ] + assert stderr in expect, repr(stderr) + + script = lstrip("""\ + import TestCommon + test = TestCommon.TestCommon(workdir='') + test.skip_test("skipping test because I said so\\n") + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "skipping test because I said so\n", stdout + stderr = run_env.stderr() + expect = [ + "NO RESULT for test at line 3 of \n", + "NO RESULT for test at line 3 of ()\n", + ] + assert stderr in expect, repr(stderr) + + import os + os.environ['TESTCOMMON_PASS_SKIPS'] = '1' + + try: + script = lstrip("""\ + import TestCommon + test = TestCommon.TestCommon(workdir='') + test.skip_test() + """) + run_env.run(program=sys.executable, stdin=script) + stdout = run_env.stdout() + assert stdout == "Skipping test.\n", stdout + stderr = run_env.stderr() + assert stderr == "PASSED\n", stderr + + finally: + del os.environ['TESTCOMMON_PASS_SKIPS'] + + + +class variables_TestCase(TestCommonTestCase): + def test_variables(self): + """Test global variables""" + run_env = self.run_env + + variables = [ + 'fail_test', + 'no_result', + 'pass_test', + 'match_exact', + 'match_re', + 'match_re_dotall', + 'python', + '_python_', + 'TestCmd', + + 'TestCommon', + 'exe_suffix', + 'obj_suffix', + 'shobj_prefix', + 'shobj_suffix', + 'lib_prefix', + 'lib_suffix', + 'dll_prefix', + 'dll_suffix', + ] + + script = "from __future__ import print_function" + \ + "import TestCommon\n" + \ + '\n'.join([ "print(TestCommon.%s)\n" % v for v in variables ]) + run_env.run(program=sys.executable, stdin=script) + stderr = run_env.stderr() + assert stderr == "", stderr + + script = "from __future__ import print_function" + \ + "from TestCommon import *\n" + \ + '\n'.join([ "print(%s)" % v for v in variables ]) + run_env.run(program=sys.executable, stdin=script) + stderr = run_env.stderr() + assert stderr == "", stderr + + + +if __name__ == "__main__": + tclasses = [ + __init__TestCase, + banner_TestCase, + must_be_writable_TestCase, + must_contain_TestCase, + must_contain_all_lines_TestCase, + must_contain_any_line_TestCase, + must_contain_exactly_lines_TestCase, + must_contain_lines_TestCase, + must_exist_TestCase, + must_exist_one_of_TestCase, + must_match_TestCase, + must_not_be_writable_TestCase, + must_not_contain_TestCase, + must_not_contain_any_line_TestCase, + must_not_contain_lines_TestCase, + must_not_exist_TestCase, + must_not_exist_any_of_TestCase, + run_TestCase, + start_TestCase, + skip_test_TestCase, + variables_TestCase, + ] + suite = unittest.TestSuite() + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests([ tclass(n) for n in names ]) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/testing/framework/TestRuntest.py b/testing/framework/TestRuntest.py new file mode 100644 index 0000000..7b3bb52 --- /dev/null +++ b/testing/framework/TestRuntest.py @@ -0,0 +1,167 @@ +""" +TestRuntest.py: a testing framework for the runtest.py command used to +invoke SCons tests. + +A TestRuntest environment object is created via the usual invocation: + + test = TestRuntest() + +TestRuntest is a subclass of TestCommon, which is in turn is a subclass +of TestCmd), and hence has available all of the methods and attributes +from those classes, as well as any overridden or additional methods or +attributes defined in this subclass. +""" + +# __COPYRIGHT__ + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import os +import os.path +import re +import shutil +import sys + +from TestCommon import * +from TestCommon import __all__ + +__all__.extend([ 'TestRuntest', + 'pythonstring', + ]) + +if re.search('\s', python): + pythonstring = _python_ +else: + pythonstring = python +pythonstring = pythonstring.replace('\\', '\\\\') + + +failing_test_template = """\ +import sys +sys.stdout.write('FAILING TEST STDOUT\\n') +sys.stderr.write('FAILING TEST STDERR\\n') +sys.exit(1) +""" + +no_result_test_template = """\ +import sys +sys.stdout.write('NO RESULT TEST STDOUT\\n') +sys.stderr.write('NO RESULT TEST STDERR\\n') +sys.exit(2) +""" + +passing_test_template = """\ +import sys +sys.stdout.write('PASSING TEST STDOUT\\n') +sys.stderr.write('PASSING TEST STDERR\\n') +sys.exit(0) +""" + +fake_scons_py = """ +__version__ = '1.2.3' +__build__ = 'D123' +__buildsys__ = 'fake_system' +__date__ = 'Jan 1 1970' +__developer__ = 'Anonymous' +""" + +fake___init___py = """ +__version__ = '4.5.6' +__build__ = 'D456' +__buildsys__ = 'another_fake_system' +__date__ = 'Dec 31 1999' +__developer__ = 'John Doe' +""" + +class TestRuntest(TestCommon): + """Class for testing the runtest.py script. + + This provides a common place for initializing Runtest tests, + eliminating the need to begin every test with the same repeated + initializations. + """ + + def __init__(self, **kw): + """Initialize a Runtest testing object. + + If they're not overridden by keyword arguments, this + initializes the object with the following default values: + + program = 'runtest.py' + interpreter = ['python', '-tt'] + match = match_exact + workdir = '' + + The workdir value means that, by default, a temporary + workspace directory is created for a TestRuntest environment. + The superclass TestCommon.__init__() will change directory (chdir) + to the workspace directory, so an explicit "chdir = '.'" on all + of the run() method calls is not necessary. This initialization + also copies the runtest.py and testing/framework/ subdirectory tree to the + temporary directory, duplicating how this test infrastructure + appears in a normal workspace. + """ + if 'program' not in kw: + kw['program'] = 'runtest.py' + if 'interpreter' not in kw: + kw['interpreter'] = [python, '-tt'] + if 'match' not in kw: + kw['match'] = match_exact + if 'workdir' not in kw: + kw['workdir'] = '' + + try: + things_to_copy = kw['things_to_copy'] + except KeyError: + things_to_copy = [ + 'runtest.py', + 'testing/framework', + ] + else: + del kw['things_to_copy'] + + orig_cwd = os.getcwd() + TestCommon.__init__(self, **kw) + + dirs = [os.environ.get('SCONS_RUNTEST_DIR', orig_cwd)] + + for thing in things_to_copy: + for dir in dirs: + t = os.path.join(dir, thing) + if os.path.exists(t): + if os.path.isdir(t): + copy_func = shutil.copytree + else: + copy_func = shutil.copyfile + copy_func(t, self.workpath(thing)) + break + + self.program_set(self.workpath(kw['program'])) + + os.environ['PYTHONPATH'] = '' + + def write_fake_scons_source_tree(self): + os.mkdir('src') + os.mkdir('src/script') + self.write('src/script/scons.py', fake_scons_py) + + os.mkdir('src/engine') + os.mkdir('src/engine/SCons') + self.write('src/engine/SCons/__init__.py', fake___init___py) + os.mkdir('src/engine/SCons/Script') + self.write('src/engine/SCons/Script/__init__.py', fake___init___py) + + def write_failing_test(self, name): + self.write(name, failing_test_template) + + def write_no_result_test(self, name): + self.write(name, no_result_test_template) + + def write_passing_test(self, name): + self.write(name, passing_test_template) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py new file mode 100644 index 0000000..ba4e06b --- /dev/null +++ b/testing/framework/TestSCons.py @@ -0,0 +1,1642 @@ +""" +TestSCons.py: a testing framework for the SCons software construction +tool. + +A TestSCons environment object is created via the usual invocation: + + test = TestSCons() + +TestScons is a subclass of TestCommon, which in turn is a subclass +of TestCmd), and hence has available all of the methods and attributes +from those classes, as well as any overridden or additional methods or +attributes defined in this subclass. +""" + +# __COPYRIGHT__ +from __future__ import division, print_function + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import os +import re +import shutil +import sys +import time +import subprocess + +from TestCommon import * +from TestCommon import __all__ + +from TestCmd import Popen +from TestCmd import PIPE + +# Some tests which verify that SCons has been packaged properly need to +# look for specific version file names. Replicating the version number +# here provides some independent verification that what we packaged +# conforms to what we expect. + +default_version = '3.1.0.alpha.yyyymmdd' + +python_version_unsupported = (2, 6, 0) +python_version_deprecated = (2, 7, 0) + +# In the checked-in source, the value of SConsVersion in the following +# line must remain "__ VERSION __" (without the spaces) so the built +# version in build/testing/framework/TestSCons.py contains the actual version +# string of the packages that have been built. +SConsVersion = '__VERSION__' +if SConsVersion == '__' + 'VERSION' + '__': + SConsVersion = default_version + +__all__.extend([ + 'TestSCons', + 'machine', + 'python', + '_exe', + '_obj', + '_shobj', + 'shobj_', + 'lib_', + '_lib', + 'dll_', + '_dll' + ]) + +machine_map = { + 'i686' : 'i386', + 'i586' : 'i386', + 'i486' : 'i386', +} + +try: + uname = os.uname +except AttributeError: + # Windows doesn't have a uname() function. We could use something like + # sys.platform as a fallback, but that's not really a "machine," so + # just leave it as None. + machine = None +else: + machine = uname()[4] + machine = machine_map.get(machine, machine) + +_exe = exe_suffix +_obj = obj_suffix +_shobj = shobj_suffix +shobj_ = shobj_prefix +_lib = lib_suffix +lib_ = lib_prefix +_dll = dll_suffix +dll_ = dll_prefix + + +if sys.platform == 'cygwin': + # On Cygwin, os.path.normcase() lies, so just report back the + # fact that the underlying Win32 OS is case-insensitive. + def case_sensitive_suffixes(s1, s2): + return 0 +else: + def case_sensitive_suffixes(s1, s2): + return (os.path.normcase(s1) != os.path.normcase(s2)) + + +file_expr = r"""File "[^"]*", line \d+, in [^\n]+ +""" + +# re.escape escapes too much. +def re_escape(str): + for c in '\\.[]()*+?': # Not an exhaustive list. + str = str.replace(c, '\\' + c) + return str + +# +# Helper functions that we use as a replacement to the default re.match +# when searching for special strings in stdout/stderr. +# +def search_re(out, l): + """ Search the regular expression 'l' in the output 'out' + and return the start index when successful. + """ + m = re.search(l, out) + if m: + return m.start() + + return None + +def search_re_in_list(out, l): + """ Search the regular expression 'l' in each line of + the given string list 'out' and return the line's index + when successful. + """ + for idx, o in enumerate(out): + m = re.search(l, o) + if m: + return idx + + return None + +# +# Helpers for handling Python version numbers +# +def python_version_string(): + return sys.version.split()[0] + +def python_minor_version_string(): + return sys.version[:3] + +def unsupported_python_version(version=sys.version_info): + return version < python_version_unsupported + +def deprecated_python_version(version=sys.version_info): + return version < python_version_deprecated + +if deprecated_python_version(): + msg = r""" +scons: warning: Support for pre-2.7.0 Python version (%s) is deprecated. + If this will cause hardship, contact scons-dev@scons.org +""" + + deprecated_python_expr = re_escape(msg % python_version_string()) + file_expr + del msg +else: + deprecated_python_expr = "" + + +def initialize_sconsflags(ignore_python_version): + """ + Add the --warn=no-python-version option to SCONSFLAGS for every + command so test scripts don't have to filter out Python version + deprecation warnings. + Same for --warn=no-visual-c-missing. + """ + save_sconsflags = os.environ.get('SCONSFLAGS') + if save_sconsflags: + sconsflags = [save_sconsflags] + else: + sconsflags = [] + if ignore_python_version and deprecated_python_version(): + sconsflags.append('--warn=no-python-version') + # Provide a way to suppress or provide alternate flags for + # TestSCons purposes by setting TESTSCONS_SCONSFLAGS. + # (The intended use case is to set it to null when running + # timing tests of earlier versions of SCons which don't + # support the --warn=no-visual-c-missing warning.) + visual_c = os.environ.get('TESTSCONS_SCONSFLAGS', + '--warn=no-visual-c-missing') + if visual_c: + sconsflags.append(visual_c) + os.environ['SCONSFLAGS'] = ' '.join(sconsflags) + return save_sconsflags + +def restore_sconsflags(sconsflags): + if sconsflags is None: + del os.environ['SCONSFLAGS'] + else: + os.environ['SCONSFLAGS'] = sconsflags + + +class TestSCons(TestCommon): + """Class for testing SCons. + + This provides a common place for initializing SCons tests, + eliminating the need to begin every test with the same repeated + initializations. + """ + + scons_version = SConsVersion + javac_is_gcj = False + + def __init__(self, **kw): + """Initialize an SCons testing object. + + If they're not overridden by keyword arguments, this + initializes the object with the following default values: + + program = 'scons' if it exists, + else 'scons.py' + interpreter = 'python' + match = match_exact + workdir = '' + + The workdir value means that, by default, a temporary workspace + directory is created for a TestSCons environment. In addition, + this method changes directory (chdir) to the workspace directory, + so an explicit "chdir = '.'" on all of the run() method calls + is not necessary. + """ + self.orig_cwd = os.getcwd() + self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) + + if not self.external: + try: + script_dir = os.environ['SCONS_SCRIPT_DIR'] + except KeyError: + pass + else: + os.chdir(script_dir) + if 'program' not in kw: + kw['program'] = os.environ.get('SCONS') + if not kw['program']: + if not self.external: + if os.path.exists('scons'): + kw['program'] = 'scons' + else: + kw['program'] = 'scons.py' + else: + kw['program'] = 'scons' + kw['interpreter'] = '' + elif not self.external and not os.path.isabs(kw['program']): + kw['program'] = os.path.join(self.orig_cwd, kw['program']) + if 'interpreter' not in kw and not os.environ.get('SCONS_EXEC'): + kw['interpreter'] = [python, '-tt'] + if 'match' not in kw: + kw['match'] = match_exact + if 'workdir' not in kw: + kw['workdir'] = '' + + # Term causing test failures due to bogus readline init + # control character output on FC8 + # TERM can cause test failures due to control chars in prompts etc. + os.environ['TERM'] = 'dumb' + + self.ignore_python_version = kw.get('ignore_python_version', 1) + if kw.get('ignore_python_version', -1) != -1: + del kw['ignore_python_version'] + + TestCommon.__init__(self, **kw) + + if not self.external: + import SCons.Node.FS + if SCons.Node.FS.default_fs is None: + SCons.Node.FS.default_fs = SCons.Node.FS.FS() + + try: + self.fixture_dirs = (os.environ['FIXTURE_DIRS']).split(os.pathsep) + except KeyError: + pass + + def Environment(self, ENV=None, *args, **kw): + """ + Return a construction Environment that optionally overrides + the default external environment with the specified ENV. + """ + if not self.external: + import SCons.Environment + import SCons.Errors + if not ENV is None: + kw['ENV'] = ENV + try: + return SCons.Environment.Environment(*args, **kw) + except (SCons.Errors.UserError, SCons.Errors.InternalError): + return None + + return None + + def detect(self, var, prog=None, ENV=None, norm=None): + """ + Detect a program named 'prog' by first checking the construction + variable named 'var' and finally searching the path used by + SCons. If either method fails to detect the program, then false + is returned, otherwise the full path to prog is returned. If + prog is None, then the value of the environment variable will be + used as prog. + """ + env = self.Environment(ENV) + if env: + v = env.subst('$'+var) + if not v: + return None + if prog is None: + prog = v + if v != prog: + return None + result = env.WhereIs(prog) + if norm and os.sep != '/': + result = result.replace(os.sep, '/') + return result + + return self.where_is(prog) + + def detect_tool(self, tool, prog=None, ENV=None): + """ + Given a tool (i.e., tool specification that would be passed + to the "tools=" parameter of Environment()) and a program that + corresponds to that tool, return true if and only if we can find + that tool using Environment.Detect(). + + By default, prog is set to the value passed into the tools parameter. + """ + + if not prog: + prog = tool + env = self.Environment(ENV, tools=[tool]) + if env is None: + return None + return env.Detect([prog]) + + def where_is(self, prog, path=None): + """ + Given a program, search for it in the specified external PATH, + or in the actual external PATH if none is specified. + """ + if path is None: + path = os.environ['PATH'] + if self.external: + if isinstance(prog, str): + prog = [prog] + import stat + paths = path.split(os.pathsep) + for p in prog: + for d in paths: + f = os.path.join(d, p) + if os.path.isfile(f): + try: + st = os.stat(f) + except OSError: + # os.stat() raises OSError, not IOError if the file + # doesn't exist, so in this case we let IOError get + # raised so as to not mask possibly serious disk or + # network issues. + continue + if stat.S_IMODE(st[stat.ST_MODE]) & 0o111: + return os.path.normpath(f) + else: + import SCons.Environment + env = SCons.Environment.Environment() + return env.WhereIs(prog, path) + + return None + + def wrap_stdout(self, build_str = "", read_str = "", error = 0, cleaning = 0): + """Wraps standard output string(s) in the normal + "Reading ... done" and "Building ... done" strings + """ + cap,lc = [ ('Build','build'), + ('Clean','clean') ][cleaning] + if error: + term = "scons: %sing terminated because of errors.\n" % lc + else: + term = "scons: done %sing targets.\n" % lc + return "scons: Reading SConscript files ...\n" + \ + read_str + \ + "scons: done reading SConscript files.\n" + \ + "scons: %sing targets ...\n" % cap + \ + build_str + \ + term + + def run(self, *args, **kw): + """ + Set up SCONSFLAGS for every command so test scripts don't need + to worry about unexpected warnings in their output. + """ + sconsflags = initialize_sconsflags(self.ignore_python_version) + try: + TestCommon.run(self, *args, **kw) + finally: + restore_sconsflags(sconsflags) + +# Modifying the options should work and ought to be simpler, but this +# class is used for more than just running 'scons' itself. If there's +# an automated way of determining whether it's running 'scons' or +# something else, this code should be resurected. +# options = kw.get('options') +# if options: +# options = [options] +# else: +# options = [] +# if self.ignore_python_version and deprecated_python_version(): +# options.append('--warn=no-python-version') +# # Provide a way to suppress or provide alternate flags for +# # TestSCons purposes by setting TESTSCONS_SCONSFLAGS. +# # (The intended use case is to set it to null when running +# # timing tests of earlier versions of SCons which don't +# # support the --warn=no-visual-c-missing warning.) +# visual_c = os.environ.get('TESTSCONS_SCONSFLAGS', +# '--warn=no-visual-c-missing') +# if visual_c: +# options.append(visual_c) +# kw['options'] = ' '.join(options) +# TestCommon.run(self, *args, **kw) + + def up_to_date(self, arguments = '.', read_str = "", **kw): + s = "" + for arg in arguments.split(): + s = s + "scons: `%s' is up to date.\n" % arg + kw['arguments'] = arguments + stdout = self.wrap_stdout(read_str = read_str, build_str = s) + # Append '.*' so that timing output that comes after the + # up-to-date output is okay. + kw['stdout'] = re.escape(stdout) + '.*' + kw['match'] = self.match_re_dotall + self.run(**kw) + + def not_up_to_date(self, arguments = '.', **kw): + """Asserts that none of the targets listed in arguments is + up to date, but does not make any assumptions on other targets. + This function is most useful in conjunction with the -n option. + """ + s = "" + for arg in arguments.split(): + s = s + "(?!scons: `%s' is up to date.)" % re.escape(arg) + s = '('+s+'[^\n]*\n)*' + kw['arguments'] = arguments + stdout = re.escape(self.wrap_stdout(build_str='ARGUMENTSGOHERE')) + kw['stdout'] = stdout.replace('ARGUMENTSGOHERE', s) + kw['match'] = self.match_re_dotall + self.run(**kw) + + def option_not_yet_implemented(self, option, arguments=None, **kw): + """ + Verifies expected behavior for options that are not yet implemented: + a warning message, and exit status 1. + """ + msg = "Warning: the %s option is not yet implemented\n" % option + kw['stderr'] = msg + if arguments: + # If it's a long option and the argument string begins with '=', + # it's of the form --foo=bar and needs no separating space. + if option[:2] == '--' and arguments[0] == '=': + kw['arguments'] = option + arguments + else: + kw['arguments'] = option + ' ' + arguments + return self.run(**kw) + + def deprecated_wrap(self, msg): + """ + Calculate the pattern that matches a deprecation warning. + """ + return '\nscons: warning: ' + re_escape(msg) + '\n' + file_expr + + def deprecated_fatal(self, warn, msg): + """ + Determines if the warning has turned into a fatal error. If so, + passes the test, as any remaining runs are now moot. + + This method expects a SConscript to be present that will causes + the warning. The method writes a SConstruct that calls the + SConsscript and looks to see what type of result occurs. + + The pattern that matches the warning is returned. + + TODO: Actually detect that it's now an error. We don't have any + cases yet, so there's no way to test it. + """ + self.write('SConstruct', """if True: + WARN = ARGUMENTS.get('WARN') + if WARN: SetOption('warn', WARN) + SConscript('SConscript') + """) + + def err_out(): + # TODO calculate stderr for fatal error + return re_escape('put something here') + + # no option, should get one of nothing, warning, or error + warning = self.deprecated_wrap(msg) + self.run(arguments = '.', stderr = None) + stderr = self.stderr() + if stderr: + # most common case done first + if match_re_dotall(stderr, warning): + # expected output + pass + elif match_re_dotall(stderr, err_out()): + # now a fatal error; skip the rest of the tests + self.pass_test() + else: + # test failed; have to do this by hand... + print(self.banner('STDOUT ')) + print(self.stdout()) + print(self.diff(warning, stderr, 'STDERR ')) + self.fail_test() + + return warning + + def deprecated_warning(self, warn, msg): + """ + Verifies the expected behavior occurs for deprecation warnings. + This method expects a SConscript to be present that will causes + the warning. The method writes a SConstruct and exercises various + combinations of command-line options and SetOption parameters to + validate that it performs correctly. + + The pattern that matches the warning is returned. + """ + warning = self.deprecated_fatal(warn, msg) + + def RunPair(option, expected): + # run the same test with the option on the command line and + # then with the option passed via SetOption(). + self.run(options = '--warn=' + option, + arguments = '.', + stderr = expected, + match = match_re_dotall) + self.run(options = 'WARN=' + option, + arguments = '.', + stderr = expected, + match = match_re_dotall) + + # all warnings off, should get no output + RunPair('no-deprecated', '') + + # warning enabled, should get expected output + RunPair(warn, warning) + + # warning disabled, should get either nothing or mandatory message + expect = """()|(Can not disable mandataory warning: 'no-%s'\n\n%s)""" % (warn, warning) + RunPair('no-' + warn, expect) + + return warning + + def diff_substr(self, expect, actual, prelen=20, postlen=40): + i = 0 + for x, y in zip(expect, actual): + if x != y: + return "Actual did not match expect at char %d:\n" \ + " Expect: %s\n" \ + " Actual: %s\n" \ + % (i, repr(expect[i-prelen:i+postlen]), + repr(actual[i-prelen:i+postlen])) + i = i + 1 + return "Actual matched the expected output???" + + def python_file_line(self, file, line): + """ + Returns a Python error line for output comparisons. + + The exec of the traceback line gives us the correct format for + this version of Python. + + File "", line 1, + + We stick the requested file name and line number in the right + places, abstracting out the version difference. + """ + # This routine used to use traceback to get the proper format + # that doesn't work well with py3. And the format of the + # traceback seems to be stable, so let's just format + # an appropriate string + # + #exec('import traceback; x = traceback.format_stack()[-1]') + # import traceback + # x = traceback.format_stack() + # x = # XXX: .lstrip() + # x = x.replace('', file) + # x = x.replace('line 1,', 'line %s,' % line) + # x="\n".join(x) + x='File "%s", line %s, in \n'%(file,line) + return x + + def normalize_ps(self, s): + s = re.sub(r'(Creation|Mod)Date: .*', + r'\1Date XXXX', s) + s = re.sub(r'%DVIPSSource:\s+TeX output\s.*', + r'%DVIPSSource: TeX output XXXX', s) + s = re.sub(r'/(BaseFont|FontName) /[A-Z0-9]{6}', + r'/\1 /XXXXXX', s) + s = re.sub(r'BeginFont: [A-Z0-9]{6}', + r'BeginFont: XXXXXX', s) + + return s + + @staticmethod + def to_bytes_re_sub(pattern, repl, str, count=0, flags=0): + """ + Wrapper around re.sub to change pattern and repl to bytes to work with + both python 2 & 3 + """ + pattern = to_bytes(pattern) + repl = to_bytes(repl) + return re.sub(pattern, repl, str, count, flags) + + def normalize_pdf(self, s): + s = self.to_bytes_re_sub(r'/(Creation|Mod)Date \(D:[^)]*\)', + r'/\1Date (D:XXXX)', s) + s = self.to_bytes_re_sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]', + r'/ID [ ]', s) + s = self.to_bytes_re_sub(r'/(BaseFont|FontName) /[A-Z]{6}', + r'/\1 /XXXXXX', s) + s = self.to_bytes_re_sub(r'/Length \d+ *\n/Filter /FlateDecode\n', + r'/Length XXXX\n/Filter /FlateDecode\n', s) + + try: + import zlib + except ImportError: + pass + else: + begin_marker = to_bytes('/FlateDecode\n>>\nstream\n') + end_marker = to_bytes('endstream\nendobj') + + encoded = [] + b = s.find(begin_marker, 0) + while b != -1: + b = b + len(begin_marker) + e = s.find(end_marker, b) + encoded.append((b, e)) + b = s.find(begin_marker, e + len(end_marker)) + + x = 0 + r = [] + for b, e in encoded: + r.append(s[x:b]) + d = zlib.decompress(s[b:e]) + d = self.to_bytes_re_sub(r'%%CreationDate: [^\n]*\n', + r'%%CreationDate: 1970 Jan 01 00:00:00\n', d) + d = self.to_bytes_re_sub(r'%DVIPSSource: TeX output \d\d\d\d\.\d\d\.\d\d:\d\d\d\d', + r'%DVIPSSource: TeX output 1970.01.01:0000', d) + d = self.to_bytes_re_sub(r'/(BaseFont|FontName) /[A-Z]{6}', + r'/\1 /XXXXXX', d) + r.append(d) + x = e + r.append(s[x:]) + s = to_bytes('').join(r) + + return s + + def paths(self,patterns): + import glob + result = [] + for p in patterns: + result.extend(sorted(glob.glob(p))) + return result + + def unlink_sconsignfile(self,name='.sconsign.dblite'): + """ + Delete sconsign file. + Note on python it seems to append .p3 to the file name so we take care of that + Parameters + ---------- + name - expected name of sconsign file + + Returns + ------- + None + """ + if sys.version_info[0] == 3: + name += '.p3' + self.unlink(name) + + def java_ENV(self, version=None): + """ + Initialize with a default external environment that uses a local + Java SDK in preference to whatever's found in the default PATH. + """ + if not self.external: + try: + return self._java_env[version]['ENV'] + except AttributeError: + self._java_env = {} + except KeyError: + pass + + import SCons.Environment + env = SCons.Environment.Environment() + self._java_env[version] = env + + + if version: + patterns = [ + '/usr/java/jdk%s*/bin' % version, + '/usr/lib/jvm/*-%s*/bin' % version, + '/usr/local/j2sdk%s*/bin' % version, + ] + java_path = self.paths(patterns) + [env['ENV']['PATH']] + else: + patterns = [ + '/usr/java/latest/bin', + '/usr/lib/jvm/*/bin', + '/usr/local/j2sdk*/bin', + ] + java_path = self.paths(patterns) + [env['ENV']['PATH']] + + env['ENV']['PATH'] = os.pathsep.join(java_path) + return env['ENV'] + + return None + + def java_where_includes(self,version=None): + """ + Return java include paths compiling java jni code + """ + import sys + + result = [] + if sys.platform[:6] == 'darwin': + java_home = self.java_where_java_home(version) + jni_path = os.path.join(java_home,'include','jni.h') + if os.path.exists(jni_path): + result.append(os.path.dirname(jni_path)) + + if not version: + version='' + jni_dirs = ['/System/Library/Frameworks/JavaVM.framework/Headers/jni.h', + '/usr/lib/jvm/default-java/include/jni.h', + '/usr/lib/jvm/java-*-oracle/include/jni.h'] + else: + jni_dirs = ['/System/Library/Frameworks/JavaVM.framework/Versions/%s*/Headers/jni.h'%version] + jni_dirs.extend(['/usr/lib/jvm/java-*-sun-%s*/include/jni.h'%version, + '/usr/lib/jvm/java-%s*-openjdk*/include/jni.h'%version, + '/usr/java/jdk%s*/include/jni.h'%version]) + dirs = self.paths(jni_dirs) + if not dirs: + return None + d=os.path.dirname(self.paths(jni_dirs)[0]) + result.append(d) + + if sys.platform == 'win32': + result.append(os.path.join(d,'win32')) + elif sys.platform.startswith('linux'): + result.append(os.path.join(d,'linux')) + return result + + def java_where_java_home(self, version=None): + if sys.platform[:6] == 'darwin': + # osx 10.11, 10.12 + home_tool = '/usr/libexec/java_home' + java_home = False + if os.path.exists(home_tool): + java_home = subprocess.check_output(home_tool).strip() + java_home = java_home.decode() + + if version is None: + if java_home: + return java_home + else: + homes = ['/System/Library/Frameworks/JavaVM.framework/Home', + # osx 10.10 + '/System/Library/Frameworks/JavaVM.framework/Versions/Current/Home'] + for home in homes: + if os.path.exists(home): + return home + + else: + if java_home.find('jdk%s'%version) != -1: + return java_home + else: + home = '/System/Library/Frameworks/JavaVM.framework/Versions/%s/Home' % version + if not os.path.exists(home): + # This works on OSX 10.10 + home = '/System/Library/Frameworks/JavaVM.framework/Versions/Current/' + else: + jar = self.java_where_jar(version) + home = os.path.normpath('%s/..'%jar) + if os.path.isdir(home): + return home + print("Could not determine JAVA_HOME: %s is not a directory" % home) + self.fail_test() + + def java_mac_check(self, where_java_bin, java_bin_name): + # on Mac there is a place holder java installed to start the java install process + # so we need to check the output in this case, more info here: + # http://anas.pk/2015/09/02/solution-no-java-runtime-present-mac-yosemite/ + sp = subprocess.Popen([where_java_bin, "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = sp.communicate() + sp.wait() + if("No Java runtime" in str(stderr)): + self.skip_test("Could not find Java " + java_bin_name + ", skipping test(s).\n") + + def java_where_jar(self, version=None): + ENV = self.java_ENV(version) + if self.detect_tool('jar', ENV=ENV): + where_jar = self.detect('JAR', 'jar', ENV=ENV) + else: + where_jar = self.where_is('jar', ENV['PATH']) + if not where_jar: + self.skip_test("Could not find Java jar, skipping test(s).\n") + elif sys.platform == "darwin": + self.java_mac_check(where_jar, 'jar') + + return where_jar + + def java_where_java(self, version=None): + """ + Return a path to the java executable. + """ + ENV = self.java_ENV(version) + where_java = self.where_is('java', ENV['PATH']) + + if not where_java: + self.skip_test("Could not find Java java, skipping test(s).\n") + elif sys.platform == "darwin": + self.java_mac_check(where_java, 'java') + + return where_java + + def java_where_javac(self, version=None): + """ + Return a path to the javac compiler. + """ + ENV = self.java_ENV(version) + if self.detect_tool('javac'): + where_javac = self.detect('JAVAC', 'javac', ENV=ENV) + else: + where_javac = self.where_is('javac', ENV['PATH']) + if not where_javac: + self.skip_test("Could not find Java javac, skipping test(s).\n") + elif sys.platform == "darwin": + self.java_mac_check(where_javac, 'javac') + + self.run(program = where_javac, + arguments = '-version', + stderr=None, + status=None) + if version: + if self.stderr().find('javac %s' % version) == -1: + fmt = "Could not find javac for Java version %s, skipping test(s).\n" + self.skip_test(fmt % version) + else: + m = re.search(r'javac (\d\.\d)', self.stderr()) + if m: + version = m.group(1) + self.javac_is_gcj = False + elif self.stderr().find('gcj'): + version='1.2' + self.javac_is_gcj = True + else: + version = None + self.javac_is_gcj = False + return where_javac, version + + def java_where_javah(self, version=None): + ENV = self.java_ENV(version) + if self.detect_tool('javah'): + where_javah = self.detect('JAVAH', 'javah', ENV=ENV) + else: + where_javah = self.where_is('javah', ENV['PATH']) + if not where_javah: + self.skip_test("Could not find Java javah, skipping test(s).\n") + return where_javah + + def java_where_rmic(self, version=None): + ENV = self.java_ENV(version) + if self.detect_tool('rmic'): + where_rmic = self.detect('RMIC', 'rmic', ENV=ENV) + else: + where_rmic = self.where_is('rmic', ENV['PATH']) + if not where_rmic: + self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n") + return where_rmic + + + def java_get_class_files(self, dir): + result = [] + for dirpath, dirnames, filenames in os.walk(dir): + for fname in filenames: + if fname.endswith('.class'): + result.append(os.path.join(dirpath, fname)) + return sorted(result) + + + def Qt_dummy_installation(self, dir='qt'): + # create a dummy qt installation + + self.subdir( dir, [dir, 'bin'], [dir, 'include'], [dir, 'lib'] ) + + self.write([dir, 'bin', 'mymoc.py'], """\ +import getopt +import sys +import re +# -w and -z are fake options used in test/QT/QTFLAGS.py +cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:wz', []) +output = None +impl = 0 +opt_string = '' +for opt, arg in cmd_opts: + if opt == '-o': output = open(arg, 'w') + elif opt == '-i': impl = 1 + else: opt_string = opt_string + ' ' + opt +output.write("/* mymoc.py%s */\\n" % opt_string) +for a in args: + contents = open(a, 'r').read() + a = a.replace('\\\\', '\\\\\\\\') + subst = r'{ my_qt_symbol( "' + a + '\\\\n" ); }' + if impl: + contents = re.sub( r'#include.*', '', contents ) + output.write(contents.replace('Q_OBJECT', subst)) +output.close() +sys.exit(0) +""") + + self.write([dir, 'bin', 'myuic.py'], """\ +import os.path +import re +import sys +output_arg = 0 +impl_arg = 0 +impl = None +source = None +opt_string = '' +for arg in sys.argv[1:]: + if output_arg: + output = open(arg, 'w') + output_arg = 0 + elif impl_arg: + impl = arg + impl_arg = 0 + elif arg == "-o": + output_arg = 1 + elif arg == "-impl": + impl_arg = 1 + elif arg[0:1] == "-": + opt_string = opt_string + ' ' + arg + else: + if source: + sys.exit(1) + source = open(arg, 'r') + sourceFile = arg +output.write("/* myuic.py%s */\\n" % opt_string) +if impl: + output.write( '#include "' + impl + '"\\n' ) + includes = re.findall('(.*?)', source.read()) + for incFile in includes: + # this is valid for ui.h files, at least + if os.path.exists(incFile): + output.write('#include "' + incFile + '"\\n') +else: + output.write( '#include "my_qobject.h"\\n' + source.read() + " Q_OBJECT \\n" ) +output.close() +sys.exit(0) +""" ) + + self.write([dir, 'include', 'my_qobject.h'], r""" +#define Q_OBJECT ; +void my_qt_symbol(const char *arg); +""") + + self.write([dir, 'lib', 'my_qobject.cpp'], r""" +#include "../include/my_qobject.h" +#include +void my_qt_symbol(const char *arg) { + fputs( arg, stdout ); +} +""") + + self.write([dir, 'lib', 'SConstruct'], r""" +env = Environment() +import sys +if sys.platform == 'win32': + env.StaticLibrary( 'myqt', 'my_qobject.cpp' ) +else: + env.SharedLibrary( 'myqt', 'my_qobject.cpp' ) +""") + + self.run(chdir = self.workpath(dir, 'lib'), + arguments = '.', + stderr = noisy_ar, + match = self.match_re_dotall) + + self.QT = self.workpath(dir) + self.QT_LIB = 'myqt' + self.QT_MOC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'mymoc.py')) + self.QT_UIC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'myuic.py')) + self.QT_LIB_DIR = self.workpath(dir, 'lib') + + def Qt_create_SConstruct(self, place): + if isinstance(place, list): + place = test.workpath(*place) + self.write(place, """\ +if ARGUMENTS.get('noqtdir', 0): QTDIR=None +else: QTDIR=r'%s' +env = Environment(QTDIR = QTDIR, + QT_LIB = r'%s', + QT_MOC = r'%s', + QT_UIC = r'%s', + tools=['default','qt']) +dup = 1 +if ARGUMENTS.get('variant_dir', 0): + if ARGUMENTS.get('chdir', 0): + SConscriptChdir(1) + else: + SConscriptChdir(0) + dup=int(ARGUMENTS.get('dup', 1)) + if dup == 0: + builddir = 'build_dup0' + env['QT_DEBUG'] = 1 + else: + builddir = 'build' + VariantDir(builddir, '.', duplicate=dup) + print(builddir, dup) + sconscript = Dir(builddir).File('SConscript') +else: + sconscript = File('SConscript') +Export("env dup") +SConscript( sconscript ) +""" % (self.QT, self.QT_LIB, self.QT_MOC, self.QT_UIC)) + + + NCR = 0 # non-cached rebuild + CR = 1 # cached rebuild (up to date) + NCF = 2 # non-cached build failure + CF = 3 # cached build failure + + if sys.platform == 'win32': + Configure_lib = 'msvcrt' + else: + Configure_lib = 'm' + + # to use cygwin compilers on cmd.exe -> uncomment following line + #Configure_lib = 'm' + + def coverage_run(self): + """ Check if the the tests are being run under coverage. + """ + return 'COVERAGE_PROCESS_START' in os.environ or 'COVERAGE_FILE' in os.environ + + def skip_if_not_msvc(self, check_platform=True): + """ Check whether we are on a Windows platform and skip the + test if not. This check can be omitted by setting + check_platform to False. + Then, for a win32 platform, additionally check + whether we have a MSVC toolchain installed + in the system, and skip the test if none can be + found (=MinGW is the only compiler available). + """ + if check_platform: + if sys.platform != 'win32': + msg = "Skipping Visual C/C++ test on non-Windows platform '%s'\n" % sys.platform + self.skip_test(msg) + return + + try: + import SCons.Tool.MSCommon as msc + if not msc.msvc_exists(): + msg = "No MSVC toolchain found...skipping test\n" + self.skip_test(msg) + except: + pass + + def checkLogAndStdout(self, checks, results, cached, + logfile, sconf_dir, sconstruct, + doCheckLog=True, doCheckStdout=True): + """ + Used to verify the expected output from using Configure() + via the contents of one or both of stdout or config.log file. + The checks, results, cached parameters all are zipped together + for use in comparing results. + + TODO: Perhaps a better API makes sense? + + Parameters + ---------- + checks : The Configure checks being run + + results : The expected results for each check + + cached : If the corresponding check is expected to be cached + + logfile : Name of the config log + + sconf_dir : Name of the sconf dir + + sconstruct : SConstruct file name + + doCheckLog : check specified log file, defaults to true + + doCheckStdout : Check stdout, defaults to true + + Returns + ------- + + """ + + class NoMatch(Exception): + def __init__(self, p): + self.pos = p + + def matchPart(log, logfile, lastEnd, NoMatch=NoMatch): + """ + Match part of the logfile + """ + m = re.match(log, logfile[lastEnd:]) + if not m: + raise NoMatch(lastEnd) + return m.end() + lastEnd + + try: + + # Build regexp for a character which is not + # a linesep, and in the case of CR/LF + # build it with both CR and CR/LF + # TODO: Not sure why this is a good idea. A static string + # could do the same since we only have two variations + # to do with? + # ls = os.linesep + # nols = "(" + # for i in range(len(ls)): + # nols = nols + "(" + # for j in range(i): + # nols = nols + ls[j] + # nols = nols + "[^" + ls[i] + "])" + # if i < len(ls)-1: + # nols = nols + "|" + # nols = nols + ")" + # + # Replaced above logic with \n as we're reading the file + # using non-binary read. Python will translate \r\n -> \n + # For us. + ls = '\n' + nols = '([^\n])' + lastEnd = 0 + + # Read the whole logfile + logfile = self.read(self.workpath(logfile), mode='r') + + # Some debug code to keep around.. + # sys.stderr.write("LOGFILE[%s]:%s"%(type(logfile),logfile)) + + if (doCheckLog and + logfile.find("scons: warning: The stored build information has an unexpected class.") >= 0): + self.fail_test() + + sconf_dir = sconf_dir + sconstruct = sconstruct + + log = r'file\ \S*%s\,line \d+:' % re.escape(sconstruct) + ls + if doCheckLog: + lastEnd = matchPart(log, logfile, lastEnd) + + log = "\t" + re.escape("Configure(confdir = %s)" % sconf_dir) + ls + if doCheckLog: + lastEnd = matchPart(log, logfile, lastEnd) + + rdstr = "" + cnt = 0 + for check,result,cache_desc in zip(checks, results, cached): + log = re.escape("scons: Configure: " + check) + ls + + if doCheckLog: + lastEnd = matchPart(log, logfile, lastEnd) + + log = "" + result_cached = 1 + for bld_desc in cache_desc: # each TryXXX + for ext, flag in bld_desc: # each file in TryBuild + file = os.path.join(sconf_dir,"conftest_%d%s" % (cnt, ext)) + if flag == self.NCR: + # NCR = Non Cached Rebuild + # rebuild will pass + if ext in ['.c', '.cpp']: + log=log + re.escape(file + " <-") + ls + log=log + r"( \|" + nols + "*" + ls + ")+?" + else: + log=log + "(" + nols + "*" + ls +")*?" + result_cached = 0 + if flag == self.CR: + # CR = cached rebuild (up to date)s + # up to date + log=log + \ + re.escape("scons: Configure: \"%s\" is up to date." + % file) + ls + log=log+re.escape("scons: Configure: The original builder " + "output was:") + ls + log=log+r"( \|.*"+ls+")+" + if flag == self.NCF: + # non-cached rebuild failure + log=log + "(" + nols + "*" + ls + ")*?" + result_cached = 0 + if flag == self.CF: + # cached rebuild failure + log=log + \ + re.escape("scons: Configure: Building \"%s\" failed " + "in a previous run and all its sources are" + " up to date." % file) + ls + log=log+re.escape("scons: Configure: The original builder " + "output was:") + ls + log=log+r"( \|.*"+ls+")+" + cnt = cnt + 1 + if result_cached: + result = "(cached) " + result + rdstr = rdstr + re.escape(check) + re.escape(result) + "\n" + log=log + re.escape("scons: Configure: " + result) + ls + ls + + if doCheckLog: + lastEnd = matchPart(log, logfile, lastEnd) + + log = "" + if doCheckLog: lastEnd = matchPart(ls, logfile, lastEnd) + if doCheckLog and lastEnd != len(logfile): + raise NoMatch(lastEnd) + + except NoMatch as m: + print("Cannot match log file against log regexp.") + print("log file: ") + print("------------------------------------------------------") + print(logfile[m.pos:]) + print("------------------------------------------------------") + print("log regexp: ") + print("------------------------------------------------------") + print(log) + print("------------------------------------------------------") + self.fail_test() + + if doCheckStdout: + exp_stdout = self.wrap_stdout(".*", rdstr) + if not self.match_re_dotall(self.stdout(), exp_stdout): + print("Unexpected stdout: ") + print("-----------------------------------------------------") + print(repr(self.stdout())) + print("-----------------------------------------------------") + print(repr(exp_stdout)) + print("-----------------------------------------------------") + self.fail_test() + + def get_python_version(self): + """ + Returns the Python version (just so everyone doesn't have to + hand-code slicing the right number of characters). + """ + # see also sys.prefix documentation + return python_minor_version_string() + + def get_platform_python_info(self): + """ + Returns a path to a Python executable suitable for testing on + this platform and its associated include path, library path, + and library name. + """ + python = self.where_is('python') + if not python: + self.skip_test('Can not find installed "python", skipping test.\n') + + self.run(program = python, stdin = """\ +import os, sys +try: + if sys.platform == 'win32': + py_ver = 'python%d%d' % sys.version_info[:2] + else: + py_ver = 'python%d.%d' % sys.version_info[:2] +except AttributeError: + py_ver = 'python' + sys.version[:3] +# print include and lib path +try: + import distutils.sysconfig + exec_prefix = distutils.sysconfig.EXEC_PREFIX + print(distutils.sysconfig.get_python_inc()) + lib_path = os.path.join(exec_prefix, 'libs') + if not os.path.exists(lib_path): + lib_path = os.path.join(exec_prefix, 'lib') + print(lib_path) +except: + print(os.path.join(sys.prefix, 'include', py_ver)) + print(os.path.join(sys.prefix, 'lib', py_ver, 'config')) +print(py_ver) +""") + + return [python] + self.stdout().strip().split('\n') + + def start(self, *args, **kw): + """ + Starts SCons in the test environment. + + This method exists to tell Test{Cmd,Common} that we're going to + use standard input without forcing every .start() call in the + individual tests to do so explicitly. + """ + if 'stdin' not in kw: + kw['stdin'] = True + sconsflags = initialize_sconsflags(self.ignore_python_version) + try: + p = TestCommon.start(self, *args, **kw) + finally: + restore_sconsflags(sconsflags) + return p + + def wait_for(self, fname, timeout=20.0, popen=None): + """ + Waits for the specified file name to exist. + """ + waited = 0.0 + while not os.path.exists(fname): + if timeout and waited >= timeout: + sys.stderr.write('timed out waiting for %s to exist\n' % fname) + if popen: + popen.stdin.close() + popen.stdin = None + self.status = 1 + self.finish(popen) + stdout = self.stdout() + if stdout: + sys.stdout.write(self.banner('STDOUT ') + '\n') + sys.stdout.write(stdout) + stderr = self.stderr() + if stderr: + sys.stderr.write(self.banner('STDERR ') + '\n') + sys.stderr.write(stderr) + self.fail_test() + time.sleep(1.0) + waited = waited + 1.0 + + def get_alt_cpp_suffix(self): + """ + Many CXX tests have this same logic. + They all needed to determine if the current os supports + files with .C and .c as different files or not + in which case they are instructed to use .cpp instead of .C + """ + if not case_sensitive_suffixes('.c','.C'): + alt_cpp_suffix = '.cpp' + else: + alt_cpp_suffix = '.C' + return alt_cpp_suffix + + def platform_has_symlink(self): + if not hasattr(os, 'symlink') or sys.platform == 'win32': + return False + else: + return True + + +class Stat: + def __init__(self, name, units, expression, convert=None): + if convert is None: + convert = lambda x: x + self.name = name + self.units = units + self.expression = re.compile(expression) + self.convert = convert + +StatList = [ + Stat('memory-initial', 'kbytes', + r'Memory before reading SConscript files:\s+(\d+)', + convert=lambda s: int(s) // 1024), + Stat('memory-prebuild', 'kbytes', + r'Memory before building targets:\s+(\d+)', + convert=lambda s: int(s) // 1024), + Stat('memory-final', 'kbytes', + r'Memory after building targets:\s+(\d+)', + convert=lambda s: int(s) // 1024), + + Stat('time-sconscript', 'seconds', + r'Total SConscript file execution time:\s+([\d.]+) seconds'), + Stat('time-scons', 'seconds', + r'Total SCons execution time:\s+([\d.]+) seconds'), + Stat('time-commands', 'seconds', + r'Total command execution time:\s+([\d.]+) seconds'), + Stat('time-total', 'seconds', + r'Total build time:\s+([\d.]+) seconds'), +] + + +class TimeSCons(TestSCons): + """Class for timing SCons.""" + def __init__(self, *args, **kw): + """ + In addition to normal TestSCons.TestSCons intialization, + this enables verbose mode (which causes the command lines to + be displayed in the output) and copies the contents of the + directory containing the executing script to the temporary + working directory. + """ + self.variables = kw.get('variables') + default_calibrate_variables = [] + if self.variables is not None: + for variable, value in self.variables.items(): + value = os.environ.get(variable, value) + try: + value = int(value) + except ValueError: + try: + value = float(value) + except ValueError: + pass + else: + default_calibrate_variables.append(variable) + else: + default_calibrate_variables.append(variable) + self.variables[variable] = value + del kw['variables'] + calibrate_keyword_arg = kw.get('calibrate') + if calibrate_keyword_arg is None: + self.calibrate_variables = default_calibrate_variables + else: + self.calibrate_variables = calibrate_keyword_arg + del kw['calibrate'] + + self.calibrate = os.environ.get('TIMESCONS_CALIBRATE', '0') != '0' + + if 'verbose' not in kw and not self.calibrate: + kw['verbose'] = True + + TestSCons.__init__(self, *args, **kw) + + # TODO(sgk): better way to get the script dir than sys.argv[0] + self.test_dir = os.path.dirname(sys.argv[0]) + test_name = os.path.basename(self.test_dir) + + if not os.path.isabs(self.test_dir): + self.test_dir = os.path.join(self.orig_cwd, self.test_dir) + self.copy_timing_configuration(self.test_dir, self.workpath()) + + def main(self, *args, **kw): + """ + The main entry point for standard execution of timings. + + This method run SCons three times: + + Once with the --help option, to have it exit after just reading + the configuration. + + Once as a full build of all targets. + + Once again as a (presumably) null or up-to-date build of + all targets. + + The elapsed time to execute each build is printed after + it has finished. + """ + if 'options' not in kw and self.variables: + options = [] + for variable, value in self.variables.items(): + options.append('%s=%s' % (variable, value)) + kw['options'] = ' '.join(options) + if self.calibrate: + self.calibration(*args, **kw) + else: + self.uptime() + self.startup(*args, **kw) + self.full(*args, **kw) + self.null(*args, **kw) + + def trace(self, graph, name, value, units, sort=None): + fmt = "TRACE: graph=%s name=%s value=%s units=%s" + line = fmt % (graph, name, value, units) + if sort is not None: + line = line + (' sort=%s' % sort) + line = line + '\n' + sys.stdout.write(line) + sys.stdout.flush() + + def report_traces(self, trace, stats): + self.trace('TimeSCons-elapsed', + trace, + self.elapsed_time(), + "seconds", + sort=0) + for name, args in stats.items(): + self.trace(name, trace, **args) + + def uptime(self): + try: + fp = open('/proc/loadavg') + except EnvironmentError: + pass + else: + avg1, avg5, avg15 = fp.readline().split(" ")[:3] + fp.close() + self.trace('load-average', 'average1', avg1, 'processes') + self.trace('load-average', 'average5', avg5, 'processes') + self.trace('load-average', 'average15', avg15, 'processes') + + def collect_stats(self, input): + result = {} + for stat in StatList: + m = stat.expression.search(input) + if m: + value = stat.convert(m.group(1)) + # The dict keys match the keyword= arguments + # of the trace() method above so they can be + # applied directly to that call. + result[stat.name] = {'value':value, 'units':stat.units} + return result + + def add_timing_options(self, kw, additional=None): + """ + Add the necessary timings options to the kw['options'] value. + """ + options = kw.get('options', '') + if additional is not None: + options += additional + kw['options'] = options + ' --debug=memory,time' + + def startup(self, *args, **kw): + """ + Runs scons with the --help option. + + This serves as a way to isolate just the amount of startup time + spent reading up the configuration, since --help exits before any + "real work" is done. + """ + self.add_timing_options(kw, ' --help') + # Ignore the exit status. If the --help run dies, we just + # won't report any statistics for it, but we can still execute + # the full and null builds. + kw['status'] = None + self.run(*args, **kw) + sys.stdout.write(self.stdout()) + stats = self.collect_stats(self.stdout()) + # Delete the time-commands, since no commands are ever + # executed on the help run and it is (or should be) always 0.0. + del stats['time-commands'] + self.report_traces('startup', stats) + + def full(self, *args, **kw): + """ + Runs a full build of SCons. + """ + self.add_timing_options(kw) + self.run(*args, **kw) + sys.stdout.write(self.stdout()) + stats = self.collect_stats(self.stdout()) + self.report_traces('full', stats) + self.trace('full-memory', 'initial', **stats['memory-initial']) + self.trace('full-memory', 'prebuild', **stats['memory-prebuild']) + self.trace('full-memory', 'final', **stats['memory-final']) + + def calibration(self, *args, **kw): + """ + Runs a full build of SCons, but only reports calibration + information (the variable(s) that were set for this configuration, + and the elapsed time to run. + """ + self.add_timing_options(kw) + self.run(*args, **kw) + for variable in self.calibrate_variables: + value = self.variables[variable] + sys.stdout.write('VARIABLE: %s=%s\n' % (variable, value)) + sys.stdout.write('ELAPSED: %s\n' % self.elapsed_time()) + + def null(self, *args, **kw): + """ + Runs an up-to-date null build of SCons. + """ + # TODO(sgk): allow the caller to specify the target (argument) + # that must be up-to-date. + self.add_timing_options(kw) + self.up_to_date(arguments='.', **kw) + sys.stdout.write(self.stdout()) + stats = self.collect_stats(self.stdout()) + # time-commands should always be 0.0 on a null build, because + # no commands should be executed. Remove it from the stats + # so we don't trace it, but only if it *is* 0 so that we'll + # get some indication if a supposedly-null build actually does + # build something. + if float(stats['time-commands']['value']) == 0.0: + del stats['time-commands'] + self.report_traces('null', stats) + self.trace('null-memory', 'initial', **stats['memory-initial']) + self.trace('null-memory', 'prebuild', **stats['memory-prebuild']) + self.trace('null-memory', 'final', **stats['memory-final']) + + def elapsed_time(self): + """ + Returns the elapsed time of the most recent command execution. + """ + return self.endTime - self.startTime + + def run(self, *args, **kw): + """ + Runs a single build command, capturing output in the specified file. + + Because this class is about timing SCons, we record the start + and end times of the elapsed execution, and also add the + --debug=memory and --debug=time options to have SCons report + its own memory and timing statistics. + """ + self.startTime = time.time() + try: + result = TestSCons.run(self, *args, **kw) + finally: + self.endTime = time.time() + return result + + def copy_timing_configuration(self, source_dir, dest_dir): + """ + Copies the timing configuration from the specified source_dir (the + directory in which the controlling script lives) to the specified + dest_dir (a temporary working directory). + + This ignores all files and directories that begin with the string + 'TimeSCons-', and all '.svn' subdirectories. + """ + for root, dirs, files in os.walk(source_dir): + if '.svn' in dirs: + dirs.remove('.svn') + dirs = [ d for d in dirs if not d.startswith('TimeSCons-') ] + files = [ f for f in files if not f.startswith('TimeSCons-') ] + for dirname in dirs: + source = os.path.join(root, dirname) + destination = source.replace(source_dir, dest_dir) + os.mkdir(destination) + if sys.platform != 'win32': + shutil.copystat(source, destination) + for filename in files: + source = os.path.join(root, filename) + destination = source.replace(source_dir, dest_dir) + shutil.copy2(source, destination) + + +# In some environments, $AR will generate a warning message to stderr +# if the library doesn't previously exist and is being created. One +# way to fix this is to tell AR to be quiet (sometimes the 'c' flag), +# but this is difficult to do in a platform-/implementation-specific +# method. Instead, we will use the following as a stderr match for +# tests that use AR so that we will view zero or more "ar: creating +# " messages to be successful executions of the test (see +# test/AR.py for sample usage). + +noisy_ar=r'(ar: creating( archive)? \S+\n?)*' + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/testing/framework/TestSConsMSVS.py b/testing/framework/TestSConsMSVS.py new file mode 100644 index 0000000..1e879d9 --- /dev/null +++ b/testing/framework/TestSConsMSVS.py @@ -0,0 +1,1177 @@ +""" +TestSConsMSVS.py: a testing framework for the SCons software construction +tool. + +A TestSConsMSVS environment object is created via the usual invocation: + + test = TestSConsMSVS() + +TestSConsMSVS is a subsclass of TestSCons, which is in turn a subclass +of TestCommon, which is in turn is a subclass of TestCmd), and hence +has available all of the methods and attributes from those classes, +as well as any overridden or additional methods or attributes defined +in this subclass. +""" + +# __COPYRIGHT__ + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import os +import sys +import platform +import traceback +from xml.etree import ElementTree + +from TestSCons import * +from TestSCons import __all__ + + + +expected_dspfile_6_0 = '''\ +# Microsoft Developer Studio Project File - Name="Test" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) External Target" 0x0106 + +CFG=Test - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Test.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Test.mak" CFG="Test - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Test - Win32 Release" (based on "Win32 (x86) External Target") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" + +!IF "$(CFG)" == "Test - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "" +# PROP BASE Intermediate_Dir "" +# PROP BASE Cmd_Line "echo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe"" +# PROP BASE Rebuild_Opt "-c && echo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe"" +# PROP BASE Target_File "Test.exe" +# PROP BASE Bsc_Name "" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "" +# PROP Intermediate_Dir "" +# PROP Cmd_Line "echo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe"" +# PROP Rebuild_Opt "-c && echo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe"" +# PROP Target_File "Test.exe" +# PROP Bsc_Name "" +# PROP Target_Dir "" + +!ENDIF + +# Begin Target + +# Name "Test - Win32 Release" + +!IF "$(CFG)" == "Test - Win32 Release" + +!ENDIF + +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE="sdk.h" +# End Source File +# End Group +# Begin Group "Local Headers" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE="test.h" +# End Source File +# End Group +# Begin Group "Other Files" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE="readme.txt" +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE="test.rc" +# End Source File +# End Group +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;l;y;def;odl;idl;hpj;bat" +# Begin Source File + +SOURCE="test.c" +# End Source File +# End Group +# Begin Source File + +SOURCE="" +# End Source File +# End Target +# End Project +''' + +expected_dswfile_6_0 = '''\ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "Test"="Test.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### +''' + +SConscript_contents_6_0 = """\ +env=Environment(platform='win32', tools=['msvs'], + MSVS_VERSION='6.0',HOST_ARCH='%(HOST_ARCH)s') + +testsrc = ['test.c'] +testincs = ['sdk.h'] +testlocalincs = ['test.h'] +testresources = ['test.rc'] +testmisc = ['readme.txt'] + +env.MSVSProject(target = 'Test.dsp', + srcs = testsrc, + incs = testincs, + localincs = testlocalincs, + resources = testresources, + misc = testmisc, + buildtarget = 'Test.exe', + variant = 'Release') +""" + + + +expected_slnfile_7_0 = """\ +Microsoft Visual Studio Solution File, Format Version 7.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "" +EndProject +Global + +\tGlobalSection(SolutionConfiguration) = preSolution +\t\tConfigName.0 = Release +\tEndGlobalSection +\tGlobalSection(ProjectDependencies) = postSolution +\tEndGlobalSection +\tGlobalSection(ProjectConfiguration) = postSolution +\t\t.Release.ActiveCfg = Release|Win32 +\t\t.Release.Build.0 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(ExtensibilityGlobals) = postSolution +\tEndGlobalSection +\tGlobalSection(ExtensibilityAddIns) = postSolution +\tEndGlobalSection +EndGlobal +""" + +expected_vcprojfile_7_0 = """\ + + +\tKeyword="MakeFileProj"> +\t +\t\t +\t +\t +\t\t +\t\t\t +\t\t +\t +\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t +\t +\t +\t + +""" + +SConscript_contents_7_0 = """\ +env=Environment(platform='win32', tools=['msvs'], + MSVS_VERSION='7.0',HOST_ARCH='%(HOST_ARCH)s') + +testsrc = ['test1.cpp', 'test2.cpp'] +testincs = ['sdk.h'] +testlocalincs = ['test.h'] +testresources = ['test.rc'] +testmisc = ['readme.txt'] + +env.MSVSProject(target = 'Test.vcproj', + slnguid = '{SLNGUID}', + srcs = testsrc, + incs = testincs, + localincs = testlocalincs, + resources = testresources, + misc = testmisc, + buildtarget = 'Test.exe', + variant = 'Release') +""" + + + +expected_slnfile_7_1 = """\ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "" +\tProjectSection(ProjectDependencies) = postProject +\tEndProjectSection +EndProject +Global + +\tGlobalSection(SolutionConfiguration) = preSolution +\t\tConfigName.0 = Release +\tEndGlobalSection +\tGlobalSection(ProjectDependencies) = postSolution +\tEndGlobalSection +\tGlobalSection(ProjectConfiguration) = postSolution +\t\t.Release.ActiveCfg = Release|Win32 +\t\t.Release.Build.0 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(ExtensibilityGlobals) = postSolution +\tEndGlobalSection +\tGlobalSection(ExtensibilityAddIns) = postSolution +\tEndGlobalSection +EndGlobal +""" + +expected_vcprojfile_7_1 = """\ + + +\tKeyword="MakeFileProj"> +\t +\t\t +\t +\t +\t\t +\t\t\t +\t\t +\t +\t +\t +\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t +\t +\t +\t + +""" + +SConscript_contents_7_1 = """\ +env=Environment(platform='win32', tools=['msvs'], + MSVS_VERSION='7.1',HOST_ARCH='%(HOST_ARCH)s') + +testsrc = ['test1.cpp', 'test2.cpp'] +testincs = ['sdk.h'] +testlocalincs = ['test.h'] +testresources = ['test.rc'] +testmisc = ['readme.txt'] + +env.MSVSProject(target = 'Test.vcproj', + slnguid = '{SLNGUID}', + srcs = testsrc, + incs = testincs, + localincs = testlocalincs, + resources = testresources, + misc = testmisc, + buildtarget = 'Test.exe', + variant = 'Release') +""" + + + +expected_slnfile_8_0 = """\ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "" +EndProject +Global + +\tGlobalSection(SolutionConfigurationPlatforms) = preSolution +\t\tRelease|Win32 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(ProjectConfigurationPlatforms) = postSolution +\t\t.Release|Win32.ActiveCfg = Release|Win32 +\t\t.Release|Win32.Build.0 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(SolutionProperties) = preSolution +\t\tHideSolutionNode = FALSE +\tEndGlobalSection +EndGlobal +""" + +expected_slnfile_9_0 = """\ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "" +EndProject +Global + +\tGlobalSection(SolutionConfigurationPlatforms) = preSolution +\t\tRelease|Win32 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(ProjectConfigurationPlatforms) = postSolution +\t\t.Release|Win32.ActiveCfg = Release|Win32 +\t\t.Release|Win32.Build.0 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(SolutionProperties) = preSolution +\t\tHideSolutionNode = FALSE +\tEndGlobalSection +EndGlobal +""" + +expected_slnfile_10_0 = """\ +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test.vcxproj", "Test.vcxproj", "{39A97E1F-1A52-8954-A0B1-A10A8487545E}" +EndProject +Global + +\tGlobalSection(SolutionConfigurationPlatforms) = preSolution +\t\tRelease|Win32 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(ProjectConfigurationPlatforms) = postSolution +\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.ActiveCfg = Release|Win32 +\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.Build.0 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(SolutionProperties) = preSolution +\t\tHideSolutionNode = FALSE +\tEndGlobalSection +EndGlobal +""" + +expected_slnfile_11_0 = """\ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 11 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test.vcxproj", "Test.vcxproj", "{39A97E1F-1A52-8954-A0B1-A10A8487545E}" +EndProject +Global + +\tGlobalSection(SolutionConfigurationPlatforms) = preSolution +\t\tRelease|Win32 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(ProjectConfigurationPlatforms) = postSolution +\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.ActiveCfg = Release|Win32 +\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.Build.0 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(SolutionProperties) = preSolution +\t\tHideSolutionNode = FALSE +\tEndGlobalSection +EndGlobal +""" + +expected_slnfile_14_0 = """\ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test.vcxproj", "Test.vcxproj", "{39A97E1F-1A52-8954-A0B1-A10A8487545E}" +EndProject +Global + +\tGlobalSection(SolutionConfigurationPlatforms) = preSolution +\t\tRelease|Win32 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(ProjectConfigurationPlatforms) = postSolution +\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.ActiveCfg = Release|Win32 +\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E}.Release|Win32.Build.0 = Release|Win32 +\tEndGlobalSection +\tGlobalSection(SolutionProperties) = preSolution +\t\tHideSolutionNode = FALSE +\tEndGlobalSection +EndGlobal +""" + +expected_vcprojfile_8_0 = """\ + + +\tKeyword="MakeFileProj"> +\t +\t\t +\t +\t +\t +\t +\t\t +\t\t\t +\t\t +\t +\t +\t +\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t +\t +\t +\t + +""" + +expected_vcprojfile_9_0 = """\ + + +\tKeyword="MakeFileProj"> +\t +\t\t +\t +\t +\t +\t +\t\t +\t\t\t +\t\t +\t +\t +\t +\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t\t +\t +\t +\t + +""" + +expected_vcprojfile_10_0 = """\ + + +\t +\t\t +\t\t\tRelease +\t\t\tWin32 +\t\t +\t +\t +\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E} + +\t\tTest +\t\tMakeFileProj +\t +\t +\t +\t\tMakefile +\t\tfalse +\t\tv100 +\t +\t +\t +\t +\t +\t\t +\t +\t +\t +\t<_ProjectFileVersion>10.0.30319.1 +\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" +\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" +\t\techo Starting SCons && "" -c "" -C "" -f SConstruct -c "Test.exe" +\t\tTest.exe +\t\tDEF1;DEF2;DEF3=1234 +\t\tinc1;inc2 +\t\t$(NMakeForcedIncludes) +\t\t$(NMakeAssemblySearchPath) +\t\t$(NMakeForcedUsingAssemblies) +\t +\t +\t\t +\t +\t +\t\t +\t +\t +\t\t +\t +\t +\t\t +\t +\t +\t\t +\t\t +\t +\t +\t\t +\t +\t +\t +\t + +""" + +expected_vcprojfile_11_0 = """\ + + +\t +\t\t +\t\t\tRelease +\t\t\tWin32 +\t\t +\t +\t +\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E} + +\t\tTest +\t\tMakeFileProj +\t +\t +\t +\t\tMakefile +\t\tfalse +\t\tv110 +\t +\t +\t +\t +\t +\t\t +\t +\t +\t +\t<_ProjectFileVersion>10.0.30319.1 +\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" +\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" +\t\techo Starting SCons && "" -c "" -C "" -f SConstruct -c "Test.exe" +\t\tTest.exe +\t\tDEF1;DEF2;DEF3=1234 +\t\tinc1;inc2 +\t\t$(NMakeForcedIncludes) +\t\t$(NMakeAssemblySearchPath) +\t\t$(NMakeForcedUsingAssemblies) +\t +\t +\t\t +\t +\t +\t\t +\t +\t +\t\t +\t +\t +\t\t +\t +\t +\t\t +\t\t +\t +\t +\t\t +\t +\t +\t +\t + +""" + +expected_vcprojfile_14_0 = """\ + + +\t +\t\t +\t\t\tRelease +\t\t\tWin32 +\t\t +\t +\t +\t\t{39A97E1F-1A52-8954-A0B1-A10A8487545E} + +\t\tTest +\t\tMakeFileProj +\t +\t +\t +\t\tMakefile +\t\tfalse +\t\tv140 +\t +\t +\t +\t +\t +\t\t +\t +\t +\t +\t<_ProjectFileVersion>10.0.30319.1 +\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" +\t\techo Starting SCons && "" -c "" -C "" -f SConstruct "Test.exe" +\t\techo Starting SCons && "" -c "" -C "" -f SConstruct -c "Test.exe" +\t\tTest.exe +\t\tDEF1;DEF2;DEF3=1234 +\t\tinc1;inc2 +\t\t$(NMakeForcedIncludes) +\t\t$(NMakeAssemblySearchPath) +\t\t$(NMakeForcedUsingAssemblies) +\t +\t +\t\t +\t +\t +\t\t +\t +\t +\t\t +\t +\t +\t\t +\t +\t +\t\t +\t\t +\t +\t +\t\t +\t +\t +\t +\t + +""" + +SConscript_contents_8_0 = """\ +env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='8.0', + CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')], + CPPPATH=['inc1', 'inc2'], + HOST_ARCH='%(HOST_ARCH)s') + +testsrc = ['test1.cpp', 'test2.cpp'] +testincs = ['sdk.h'] +testlocalincs = ['test.h'] +testresources = ['test.rc'] +testmisc = ['readme.txt'] + +env.MSVSProject(target = 'Test.vcproj', + slnguid = '{SLNGUID}', + srcs = testsrc, + incs = testincs, + localincs = testlocalincs, + resources = testresources, + misc = testmisc, + buildtarget = 'Test.exe', + variant = 'Release') +""" + +SConscript_contents_9_0 = """\ +env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='9.0', + CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')], + CPPPATH=['inc1', 'inc2'], + HOST_ARCH='%(HOST_ARCH)s') + +testsrc = ['test1.cpp', 'test2.cpp'] +testincs = ['sdk.h'] +testlocalincs = ['test.h'] +testresources = ['test.rc'] +testmisc = ['readme.txt'] + +env.MSVSProject(target = 'Test.vcproj', + slnguid = '{SLNGUID}', + srcs = testsrc, + incs = testincs, + localincs = testlocalincs, + resources = testresources, + misc = testmisc, + buildtarget = 'Test.exe', + variant = 'Release') +""" + +SConscript_contents_10_0 = """\ +env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='10.0', + CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')], + CPPPATH=['inc1', 'inc2'], + HOST_ARCH='%(HOST_ARCH)s') + +testsrc = ['test1.cpp', 'test2.cpp'] +testincs = ['sdk_dir\sdk.h'] +testlocalincs = ['test.h'] +testresources = ['test.rc'] +testmisc = ['readme.txt'] + +env.MSVSProject(target = 'Test.vcxproj', + slnguid = '{SLNGUID}', + srcs = testsrc, + incs = testincs, + localincs = testlocalincs, + resources = testresources, + misc = testmisc, + buildtarget = 'Test.exe', + variant = 'Release') +""" + +SConscript_contents_11_0 = """\ +env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='11.0', + CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')], + CPPPATH=['inc1', 'inc2'], + HOST_ARCH='%(HOST_ARCH)s') + +testsrc = ['test1.cpp', 'test2.cpp'] +testincs = ['sdk_dir\sdk.h'] +testlocalincs = ['test.h'] +testresources = ['test.rc'] +testmisc = ['readme.txt'] + +env.MSVSProject(target = 'Test.vcxproj', + slnguid = '{SLNGUID}', + srcs = testsrc, + incs = testincs, + localincs = testlocalincs, + resources = testresources, + misc = testmisc, + buildtarget = 'Test.exe', + variant = 'Release') +""" + +SConscript_contents_14_0 = """\ +env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='14.0', + CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')], + CPPPATH=['inc1', 'inc2'], + HOST_ARCH='%(HOST_ARCH)s') + +testsrc = ['test1.cpp', 'test2.cpp'] +testincs = ['sdk_dir\sdk.h'] +testlocalincs = ['test.h'] +testresources = ['test.rc'] +testmisc = ['readme.txt'] + +env.MSVSProject(target = 'Test.vcxproj', + slnguid = '{SLNGUID}', + srcs = testsrc, + incs = testincs, + localincs = testlocalincs, + resources = testresources, + misc = testmisc, + buildtarget = 'Test.exe', + variant = 'Release') +""" + +class TestSConsMSVS(TestSCons): + """Subclass for testing MSVS-specific portions of SCons.""" + + def msvs_versions(self): + if not hasattr(self, '_msvs_versions'): + + # Determine the SCons version and the versions of the MSVS + # environments installed on the test machine. + # + # We do this by executing SCons with an SConstruct file + # (piped on stdin) that spits out Python assignments that + # we can just exec(). We construct the SCons.__"version"__ + # string in the input here so that the SCons build itself + # doesn't fill it in when packaging SCons. + input = """\ +import SCons +import SCons.Tool.MSCommon +print("self.scons_version =%%s"%%repr(SCons.__%s__)) +print("self._msvs_versions =%%s"%%str(SCons.Tool.MSCommon.query_versions())) +""" % 'version' + + self.run(arguments = '-n -q -Q -f -', stdin = input) + exec(self.stdout()) + + return self._msvs_versions + + def vcproj_sys_path(self, fname): + """ + """ + orig = 'sys.path = [ join(sys' + + enginepath = repr(os.path.join(self._cwd, '..', 'engine')) + replace = 'sys.path = [ %s, join(sys' % enginepath + + contents = self.read(fname, mode='r') + contents = contents.replace(orig, replace) + self.write(fname, contents) + + def msvs_substitute(self, input, msvs_ver, + subdir=None, sconscript=None, + python=None, + project_guid=None, + vcproj_sccinfo='', sln_sccinfo=''): + if not hasattr(self, '_msvs_versions'): + self.msvs_versions() + + if subdir: + workpath = self.workpath(subdir) + else: + workpath = self.workpath() + + if sconscript is None: + sconscript = self.workpath('SConstruct') + + if python is None: + python = sys.executable + + if project_guid is None: + project_guid = "{E5466E26-0003-F18B-8F8A-BCD76C86388D}" + + if 'SCONS_LIB_DIR' in os.environ: + exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % os.environ['SCONS_LIB_DIR'] + else: + exec_script_main = "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-%s'), join(sys.prefix, 'scons-%s'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()" % (self.scons_version, self.scons_version) + exec_script_main_xml = exec_script_main.replace("'", "'") + + result = input.replace(r'', workpath) + result = result.replace(r'', python) + result = result.replace(r'', sconscript) + result = result.replace(r'', exec_script_main) + result = result.replace(r'', exec_script_main_xml) + result = result.replace(r'', project_guid) + result = result.replace('\n', vcproj_sccinfo) + result = result.replace('\n', sln_sccinfo) + return result + + def get_msvs_executable(self, version): + """Returns a full path to the executable (MSDEV or devenv) + for the specified version of Visual Studio. + """ + from SCons.Tool.MSCommon import get_vs_by_version + + msvs = get_vs_by_version(version) + if not msvs: + return None + return msvs.get_executable() + + def run(self, *args, **kw): + """ + Suppress MSVS deprecation warnings. + """ + save_sconsflags = os.environ.get('SCONSFLAGS') + if save_sconsflags: + sconsflags = [save_sconsflags] + else: + sconsflags = [] + sconsflags = sconsflags + ['--warn=no-deprecated'] + os.environ['SCONSFLAGS'] = ' '.join(sconsflags) + try: + result = TestSCons.run(self, *args, **kw) + finally: + os.environ['SCONSFLAGS'] = save_sconsflags or '' + return result + + def get_vs_host_arch(self): + """ Get an MSVS, SDK , and/or MSVS acceptable platform arch + """ + + # Dict to 'canonalize' the arch + _ARCH_TO_CANONICAL = { + "x86": "x86", + "amd64": "amd64", + "i386": "x86", + "emt64": "amd64", + "x86_64": "amd64", + "itanium": "ia64", + "ia64": "ia64", + } + + host_platform = platform.machine() + # TODO(2.5): the native Python platform.machine() function returns + # '' on all Python versions before 2.6, after which it also uses + # PROCESSOR_ARCHITECTURE. + if not host_platform: + host_platform = os.environ.get('PROCESSOR_ARCHITECTURE', '') + + + try: + host = _ARCH_TO_CANONICAL[host_platform] + except KeyError as e: + # Default to x86 for all other platforms + host = 'x86' + + + return host + + def validate_msvs_file(self, file): + try: + x = ElementTree.parse(file) + except: + print("--------------------------------------------------------------") + print("--------------------------------------------------------------") + print(traceback.format_exc()) + print("Failed to validate xml in MSVS file: ") + print(file) + print("--------------------------------------------------------------") + print("--------------------------------------------------------------") + self.fail_test() +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/testing/framework/TestSCons_time.py b/testing/framework/TestSCons_time.py new file mode 100644 index 0000000..bc116ec --- /dev/null +++ b/testing/framework/TestSCons_time.py @@ -0,0 +1,331 @@ +""" +TestSCons_time.py: a testing framework for the scons-test.py script + +A TestSCons_time environment object is created via the usual invocation: + + test = TestSCons_time() + +TestSCons_time is a subclass of TestCommon, which is in turn is a subclass +of TestCmd), and hence has available all of the methods and attributes +from those classes, as well as any overridden or additional methods or +attributes defined in this subclass. +""" + +# __COPYRIGHT__ + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import os +import os.path +import sys + +from TestCommon import * +from TestCommon import __all__ +# some of the scons_time tests may need regex-based matching: +from TestSCons import search_re, search_re_in_list + +__all__.extend([ 'TestSCons_time', + ]) + +SConstruct = """\ +from __future__ import print_function +import os +print("SConstruct file directory:", os.getcwd()) +""" + +scons_py = """\ +#!/usr/bin/env python +import os +import sys +def write_args(fp, args): + fp.write(args[0] + '\\n') + for arg in args[1:]: + fp.write(' ' + arg + '\\n') +write_args(sys.stdout, sys.argv) +for arg in sys.argv[1:]: + if arg[:10] == '--profile=': + profile = open(arg[10:], 'w') + profile.write('--profile\\n') + write_args(profile, sys.argv) + break +sys.stdout.write('SCONS_LIB_DIR = ' + os.environ['SCONS_LIB_DIR'] + '\\n') +exec(open('SConstruct', 'r').read()) +""" + +aegis_py = """\ +#!/usr/bin/env python +import os +import sys +script_dir = 'src/script' +if not os.path.exists(script_dir): + os.makedirs(script_dir) +open(script_dir + '/scons.py', 'w').write( +r'''%s''') +""" % scons_py + + +svn_py = """\ +#!/usr/bin/env python +import os +import sys +dir = sys.argv[-1] +script_dir = dir + '/src/script' +os.makedirs(script_dir) +open(script_dir + '/scons.py', 'w').write( +r'''%s''') +""" % scons_py + + +logfile_contents = """\ +Memory before reading SConscript files: 100%(index)s +Memory after reading SConscript files: 200%(index)s +Memory before building targets: 300%(index)s +Memory after building targets: 400%(index)s +Object counts: + pre- post- pre- post- + read read build build Class + 101%(index)s 102%(index)s 103%(index)s 104%(index)s Action.CommandAction + 201%(index)s 202%(index)s 203%(index)s 204%(index)s Action.CommandGeneratorAction + 301%(index)s 302%(index)s 303%(index)s 304%(index)s Action.FunctionAction + 401%(index)s 402%(index)s 403%(index)s 404%(index)s Action.LazyAction + 501%(index)s 502%(index)s 503%(index)s 504%(index)s Action.ListAction + 601%(index)s 602%(index)s 603%(index)s 604%(index)s Builder.BuilderBase + 701%(index)s 702%(index)s 703%(index)s 704%(index)s Builder.CompositeBuilder + 801%(index)s 802%(index)s 803%(index)s 804%(index)s Builder.ListBuilder + 901%(index)s 902%(index)s 903%(index)s 904%(index)s Builder.MultiStepBuilder + 1001%(index)s 1002%(index)s 1003%(index)s 1004%(index)s Builder.OverrideWarner + 1101%(index)s 1102%(index)s 1103%(index)s 1104%(index)s Environment.Base + 1201%(index)s 1202%(index)s 1203%(index)s 1204%(index)s Environment.EnvironmentClone + 1301%(index)s 1302%(index)s 1303%(index)s 1304%(index)s Environment.OverrideEnvironment + 1401%(index)s 1402%(index)s 1403%(index)s 1404%(index)s Executor.Executor + 1501%(index)s 1502%(index)s 1503%(index)s 1504%(index)s Node.FS + 1601%(index)s 1602%(index)s 1603%(index)s 1604%(index)s Node.FS.Base + 1701%(index)s 1702%(index)s 1703%(index)s 1704%(index)s Node.FS.Dir + 1801%(index)s 1802%(index)s 1803%(index)s 1804%(index)s Node.FS.File + 1901%(index)s 1902%(index)s 1904%(index)s 1904%(index)s Node.FS.RootDir + 2001%(index)s 2002%(index)s 2003%(index)s 2004%(index)s Node.Node +Total build time: 11.123456 seconds +Total SConscript file execution time: 22.234567 seconds +Total SCons execution time: 33.345678 seconds +Total command execution time: 44.456789 seconds +""" + + +profile_py = """\ +%(body)s + +import profile + +try: dispatch = profile.Profile.dispatch +except AttributeError: pass +else: dispatch['c_exception'] = profile.Profile.trace_dispatch_return + +prof = profile.Profile() +prof.runcall(%(call)s) +prof.dump_stats(r'%(profile_name)s') +""" + + +class TestSCons_time(TestCommon): + """Class for testing the scons-time script. + + This provides a common place for initializing scons-time tests, + eliminating the need to begin every test with the same repeated + initializations. + """ + + def __init__(self, **kw): + """Initialize an SCons_time testing object. + + If they're not overridden by keyword arguments, this + initializes the object with the following default values: + + program = 'scons-time' + interpreter = ['python', '-tt'] + match = match_exact + workdir = '' + + The workdir value means that, by default, a temporary workspace + directory is created for a TestSCons_time environment. + In addition, this method changes directory (chdir) to the + workspace directory, so an explicit "chdir = '.'" on all of the + run() method calls is not necessary. + """ + + self.orig_cwd = os.getcwd() + try: + script_dir = os.environ['SCONS_SCRIPT_DIR'] + except KeyError: + pass + else: + os.chdir(script_dir) + if 'program' not in kw: + p = os.environ.get('SCONS_TIME') + if not p: + p = 'scons-time' + if not os.path.exists(p): + p = 'scons-time.py' + kw['program'] = p + + if 'interpreter' not in kw: + kw['interpreter'] = [python, '-tt'] + + if 'match' not in kw: + kw['match'] = match_exact + + if 'workdir' not in kw: + kw['workdir'] = '' + + TestCommon.__init__(self, **kw) + + def archive_split(self, path): + if path[-7:] == '.tar.gz': + return path[:-7], path[-7:] + else: + return os.path.splitext(path) + + def fake_logfile(self, logfile_name, index=0): + self.write(self.workpath(logfile_name), logfile_contents % locals()) + + def profile_data(self, profile_name, python_name, call, body): + profile_name = self.workpath(profile_name) + python_name = self.workpath(python_name) + d = { + 'profile_name' : profile_name, + 'python_name' : python_name, + 'call' : call, + 'body' : body, + } + self.write(python_name, profile_py % d) + self.run(program = python_name, interpreter = sys.executable) + + def tempdir_re(self, *args): + """ + Returns a regular expression to match a scons-time + temporary directory. + """ + import re + import tempfile + + sep = re.escape(os.sep) + tempdir = tempfile.gettempdir() + + try: + realpath = os.path.realpath + except AttributeError: + pass + else: + tempdir = realpath(tempdir) + + args = (tempdir, 'scons-time-',) + args + x = os.path.join(*args) + x = re.escape(x) + x = x.replace('time\\-', 'time\\-[^%s]*' % sep) + return x + + def write_fake_aegis_py(self, name): + name = self.workpath(name) + self.write(name, aegis_py) + os.chmod(name, 0o755) + return name + + def write_fake_scons_py(self): + self.subdir('src', ['src', 'script']) + self.write('src/script/scons.py', scons_py) + + def write_fake_svn_py(self, name): + name = self.workpath(name) + self.write(name, svn_py) + os.chmod(name, 0o755) + return name + + def write_sample_directory(self, archive, dir, files): + dir = self.workpath(dir) + for name, content in files: + path = os.path.join(dir, name) + d, f = os.path.split(path) + if not os.path.isdir(d): + os.makedirs(d) + open(path, 'w').write(content) + return dir + + def write_sample_tarfile(self, archive, dir, files): + import shutil + try: + import tarfile + + except ImportError: + + self.skip_test('no tarfile module\n') + + else: + + base, suffix = self.archive_split(archive) + + mode = { + '.tar' : 'w', + '.tar.gz' : 'w:gz', + '.tgz' : 'w:gz', + } + + tar = tarfile.open(archive, mode[suffix]) + for name, content in files: + path = os.path.join(dir, name) + open(path, 'wb').write(bytearray(content,'utf-8')) + tarinfo = tar.gettarinfo(path, path) + tarinfo.uid = 111 + tarinfo.gid = 111 + tarinfo.uname = 'fake_user' + tarinfo.gname = 'fake_group' + tar.addfile(tarinfo, open(path, 'rb')) + tar.close() + shutil.rmtree(dir) + return self.workpath(archive) + + def write_sample_zipfile(self, archive, dir, files): + import shutil + try: + import zipfile + except ImportError: + + sys.stderr.write('no zipfile module\n') + self.no_result() + + else: + + zip = zipfile.ZipFile(archive, 'w') + for name, content in files: + path = os.path.join(dir, name) + open(path, 'w').write(content) + zip.write(path) + zip.close() + shutil.rmtree(dir) + return self.workpath(archive) + + sample_project_files = [ + ('SConstruct', SConstruct), + ] + + def write_sample_project(self, archive, dir=None): + base, suffix = self.archive_split(archive) + + write_sample = { + '.tar' : self.write_sample_tarfile, + '.tar.gz' : self.write_sample_tarfile, + '.tgz' : self.write_sample_tarfile, + '.zip' : self.write_sample_zipfile, + }.get(suffix, self.write_sample_directory) + + if not dir: + dir = base + + os.mkdir(dir) + path = write_sample(archive, dir, self.sample_project_files) + + return path + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/testing/framework/TestSConsign.py b/testing/framework/TestSConsign.py new file mode 100644 index 0000000..a48b648 --- /dev/null +++ b/testing/framework/TestSConsign.py @@ -0,0 +1,90 @@ +# __COPYRIGHT__ +from __future__ import print_function + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +__doc__ = """ +TestSConsign.py: a testing framework for the "sconsign" script +tool. + +A TestSConsign environment object is created via the usual invocation: + + test = TestSConsign() + +TestSconsign is a subclass of TestSCons, which is a subclass of +TestCommon, which is in turn is a subclass of TestCmd), and hence +has available all of the methods and attributes from those classes, +as well as any overridden or additional methods or attributes defined +in this subclass. +""" + +import os +import os.path +import sys + +from TestSCons import * +from TestSCons import __all__ + +__all__.extend([ 'TestSConsign', ]) + +class TestSConsign(TestSCons): + """Class for testing the sconsign.py script. + + This provides a common place for initializing sconsign tests, + eliminating the need to begin every test with the same repeated + initializations. + + This adds additional methods for running the sconsign script + without changing the basic ability of the run() method to run + "scons" itself, since we need to run scons to generate the + .sconsign files that we want the sconsign script to read. + """ + def __init__(self, *args, **kw): + try: + script_dir = os.environ['SCONS_SCRIPT_DIR'] + except KeyError: + pass + else: + os.chdir(script_dir) + self.script_dir = os.getcwd() + + TestSCons.__init__(self, *args, **kw) + + self.my_kw = { + 'interpreter' : python, # imported from TestSCons + } + + if 'program' not in kw: + kw['program'] = os.environ.get('SCONS') + if not kw['program']: + if os.path.exists('scons'): + kw['program'] = 'scons' + else: + kw['program'] = 'scons.py' + + sconsign = os.environ.get('SCONSIGN') + if not sconsign: + if os.path.exists(self.script_path('sconsign.py')): + sconsign = 'sconsign.py' + elif os.path.exists(self.script_path('sconsign')): + sconsign = 'sconsign' + else: + print("Can find neither 'sconsign.py' nor 'sconsign' scripts.") + self.no_result() + self.set_sconsign(sconsign) + + def script_path(self, script): + return os.path.join(self.script_dir, script) + + def set_sconsign(self, sconsign): + self.my_kw['program'] = sconsign + + def run_sconsign(self, *args, **kw): + kw.update(self.my_kw) + return self.run(*args, **kw) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/testing/framework/test-framework.rst b/testing/framework/test-framework.rst new file mode 100644 index 0000000..7082eb9 --- /dev/null +++ b/testing/framework/test-framework.rst @@ -0,0 +1,427 @@ +======================= +SCons Testing Framework +======================= + +SCons uses extensive automated tests to try to ensure quality. The primary goal +is that users should be able to upgrade from version to version without any surprise +changes in behavior. + +In general, no change goes into SCons unless it has one or more new or modified +tests that demonstrably exercise the bug being fixed or the feature being added. +There are exceptions to this guideline, but they should be just that, ''exceptions''. +When in doubt, make sure it's tested. + +Test Organization +================= + +There are three types of SCons tests: + +*End-to-End Tests* + End-to-end tests of SCons are all Python scripts (``*.py``) underneath + the ``test/`` subdirectory. They use the test infrastructure modules in the + ``testing/framework`` subdirectory. + +*Unit Tests* + Unit tests for individual SCons modules live underneath the + ``src/engine/`` subdirectory and are the same base name as the module + with ``Tests.py`` appended--for example, the unit tests for the + ``Builder.py`` module are in the ``BuilderTests.py`` script. + +*External Tests* + For the support of external Tools (in the form of packages, preferably), the + testing framework got extended, such that it can run in standalone mode. + You can start it from the top-level folder of your Tool's source tree, + where it then finds all Python scripts (``*.py``) underneath the + local ``test/`` directory. + This implies that Tool tests have to be kept in a folder named ``test``, + like for the SCons core. + + +Contrasting End-to-End and Unit Tests +##################################### + +In general, anything that we've put into an end-to-end test script should +be considered a hardened part of the interface (that is, it's something +that a user might do) and should not be broken. Unit tests are now +considered more malleable, more for testing internal interfaces that +can change so long as we don't break users' ``SConscript`` files. (This +wasn't always the case, and there's a lot of meaty code in many of the +unit test scripts that does, in fact, capture external interface +behavior. In general, we should try to move those things to end-to-end +scripts as we find them.) + +It's more difficult to debug end-to-end tests. You can actually go +straight into the Python debugger on the unit test scripts by using the +``runtest.py --pdb`` option, but the end-to-end tests treat an SCons +invocation as a "black box" and just look for external effects. +Simple ``print`` statements within the SCons code itself often don't help +debug end-to-end because they end up in SCons output that gets compared +against expected output and cause a test failure. Probably the most +effective technique is to use the internal ``SCons.Debug.Trace()`` function, +which prints output to ``/dev/tty`` on Linux/UNIX systems and ``con`` on +Windows systems, so you can see what's going on. + +Naming conventions +################## + +The end-to-end tests, more or less, stick to the following naming conventions: + +1. All tests end with a .py suffix. + +2. In the *General* form we use + + ``Feature.py`` + for the test of a specified feature; try to + keep this description reasonably short + + ``Feature-x.py`` + for the test of a specified feature using + option ``x`` + +3. The *command line option* tests take the form + + ``option-x.py`` + for a lower-case single-letter option + + ``option--X.py`` + upper-case single-letter option + (with an extra hyphen, so the file names will + be unique on case-insensitive systems) + + ``option--lo.py`` + long option; abbreviate the long + option name to a few characters + + +Running Tests +============= + +The standard set of SCons tests are run from the top-level source directory +by the ``runtest.py`` script. + +Help is available through the ``-h`` option: + +:: + + $ python runtest.py -h + +To simply run all the tests, use the ``-a`` option: + +:: + + $ python runtest.py -a + +By default, ``runtest.py`` prints a count and percentage message for each test +case, along with the name of the test file. +If you need the output to be more silent, have a look at the ``-q``, ``-s`` and +``-k`` options. + +You may specifically list one or more tests to be run: + +:: + + $ python runtest.py src/engine/SCons/BuilderTests.py + $ python runtest.py test/option-j.py test/Program.py + +Folder names are allowed arguments as well, so you can do a + +:: + + $ python runtest.py test/SWIG + +to run all SWIG tests only. + +You can also use the ``-f`` option to execute just the tests listed in a specified +text file: + +:: + + $ cat testlist.txt + test/option-j.py + test/Program.py + $ python runtest.py -f testlist.txt + + +One test must be listed per line, and any lines that begin with '#' +will be ignored (the intent being to allow you, for example, +to comment out tests that +are currently passing and then uncomment all of the tests in the file +for a final validation run). + +If more than one test is run, the ``runtest.py`` script prints a summary +of how many tests passed, failed, or yielded no result, and lists any +unsuccessful tests. + +The above invocations all test directly the files underneath the ``src/`` +subdirectory, and do not require that a packaging build be performed first. +The ``runtest.py`` script supports additional options to run tests against +unpacked packages in the ``build/test-*/`` subdirectories. + +If you are testing a separate Tool outside of the SCons source tree, you have +to call the ``runtest.py`` script in *external* (stand-alone) mode:: + + $ python ~/scons/runtest.py -e -a + +. This ensures that the testing framework doesn't try to access SCons classes +needed for some of the *internal* test cases. + +Note, that the actual tests are carried out in a temporary folder each, which gets +deleted afterwards. This ensures that your source directories don't get clobbered +with temporary files from the test runs. It also means that you can't simply change +into a folder to "debug things" after a test has gone wrong. For a way around this, +check out the ``PRESERVE`` environment variable. It can be seen in action in +`How to convert old tests`_ below. + +Not Running Tests +================= + +If you simply want to check which tests would get executed, you can call the +``runtest.py`` script with the ``-l`` option:: + + $ python runtest.py -l + +Then there is also the ``-n`` option, which prints the command line for each +single test, but doesn't actually execute them:: + + $ python runtest.py -n + +Finding Tests +============= + +When started in *standard* mode + +:: + + $ python runtest.py -a + + +, ``runtest.py`` assumes that it is run from the SCons top-level source directory. +It then dives into the ``src`` and ``test`` folders, where it tries to find filenames + + ``*Test.py`` + for the ``src`` directory, and + + ``*.py`` + for the ``test`` folder. + +When using fixtures, you may quickly end up in a position where you have supporting +Python script files in a subfolder, but they shouldn't get picked up as test scripts. +In this case you have two options: + +1. Add a file with the name ``sconstest.skip`` to your subfolder. This lets + ``runtest.py`` skip the contents of the directory completely. +2. Create a file ``.exclude_tests`` in each folder in question, and in it list + line-by-line the files to get excluded from testing. + +The same rules apply when testing external Tools by using the ``-e`` option. + + +"Hello, world!" SCons Test Script +================================= + +To illustrate how the end-to-end test scripts work, +let's walk through a simple "Hello, world!" example: + +:: + + #!python + import TestSCons + + test = TestSCons.TestSCons() + + test.write('SConstruct', """\ + Program('hello.c') + """) + + test.write('hello.c', """\ + int + main(int argc, char *argv[]) + { + printf("Hello, world!\\n"); + exit (0); + } + """) + + test.run() + + test.run(program='./hello', stdout="Hello, world!\n") + + test.pass_test() + + +``import TestSCons`` + Imports the main infrastructure for writing SCons tests. This is normally the only part of the infrastructure that needs importing. Sometimes other Python modules are necessary or helpful, and get imported before this line. + +``test = TestSCons.TestSCons()`` + This initializes an object for testing. A fair amount happens under the covers when the object is created, including: + + * A temporary directory is created for all the in-line files that will get created. + * The temporary directory's removal is arranged for when the test is finished. + * We ``os.chdir()`` to the temporary directory. + +``test.write('SConstruct', ...`` + This line creates an ``SConstruct`` file in the temporary directory, to be used as input to the ``scons`` run(s) that we're testing. Note the use of the Python triple-quote syntax for the contents of the ``SConstruct`` file. Because input files for tests are all created from in-line data like this, the tests can sometimes get a little confusing to read, because some of the Python code is found + +``test.write('hello.c', ...`` + This lines creates an ``hello.c`` file in the temporary directory. Note that we have to escape the ``\\n`` in the ``"Hello, world!\\n"`` string so that it ends up as a single backslash in the ``hello.c`` file on disk. + +``test.run()`` + This actually runs SCons. Like the object initialization, things happen under the covers: + + * The exit status is verified; the test exits with a failure if the exit status is not zero. + * The error output is examined, and the test exits with a failure if there is any + +``test.run(program='./hello', stdout="Hello, world!\n")`` + This shows use of the ``TestSCons.run()`` method to execute a program other than ``scons``, in this case the ``hello`` program we just presumably built. The ``stdout=`` keyword argument also tells the ``TestSCons.run()`` method to fail if the program output does not match the expected string ``"Hello, world!\n"``. Like the previous ``test.run()`` line, it will also fail the test if the exit status is non-zero, or there is any error output. + +``test.pass_test()`` + This is always the last line in a test script. It prints ``PASSED`` on the screen and makes sure we exit with a ``0`` status to indicate the test passed. As a side effect of destroying the ``test`` object, the created temporary directory will be removed. + +Working with fixtures +===================== + +In the simple example above, we have seen how to create files in the temporary test directory. +We give a filename to the ``TestSCons.write()`` method, together with its contents, and it gets +written to the test folder right before its start. + +This technique can still be seen throughout most of the end-to-end tests, but there is a better +way. It's much easier to edit, create and maintain real files, instead of copy/pasting +content to/from a Python script. If the test files get longer, the test script +gets longer and is harder to read. + +Against this, we now have the possibility to copy single files or the contents of a +local folder to the test directory. Since we can reuse these files/folders to setup +several tests, we call them *fixtures* in the following. + +Directory fixtures +################## + +The function ``dir_fixture(self, srcdir, dstdir=None)`` in the ``TestCmd`` class +copies the contents of the specified folder ``srcdir`` from +the directory of the called test script, to the current +temporary test directory. +The ``srcdir`` name may be a list, in which case the elements are +concatenated with the ``os.path.join()`` method. The ``dstdir`` is +assumed to be under the temporary working directory, it gets +created automatically, if it does not already exist. + +A short syntax example:: + + test = TestSCons.TestSCons() + test.dir_fixture('image') + test.run() + +would copy all files and subfolders from the local ``image`` folder, to +the temporary directory for the current test. + +If you'd like to see a real example for this in action, refer to the test +named ``test/packaging/convenience-functions/convenience-functions.py``. + +File fixtures +############# + +Like for directory fixtures, ``file_fixture(self, srcfile, dstfile=None)`` +copies the file ``srcfile`` from the directory of +the called script, to the temporary test directory. +The ``dstfile`` is assumed to be under the temporary working +directory, unless it is an absolute path name. +If ``dstfile`` is specified, its target directory gets created +automatically if it doesn't already exist. + +With a:: + + test = TestSCons.TestSCons() + test.file_fixture('SConstruct') + test.file_fixture(['src','main.cpp'],['src','main.cpp']) + test.run() + +you would copy the files ``SConstruct`` and ``src/main.cpp`` to the temporary +test folder, prior to running the test itself. + +Again, a reference example can be found in the current *default* revision of +SCons, it is ``test/packaging/sandbox-test/sandbox-test.py``. + +For even more examples you should check out one of the external Tools, e.g. the +*Qt4* Tool at https://bitbucket.org/dirkbaechle/scons_qt4. Also visit the SCons +Tools Index at https://github.com/SCons/scons/wiki/ToolsIndex for a complete +list of available Tools, though not all may have tests yet. + +How to convert old tests +######################## + +We now show how to convert a test, still using the ``TestSCons.write()`` method, to +the fixture based approach. For this, we need to get at the files as they +are written to each temporary test folder. + +Luckily, ``runtest.py`` checks for the existence of an environment variable named +``PRESERVE``. If it is set to a non-zero value, the testing framework doesn't delete +the test folder as ususal, but prints its name to the screen. + +So, you should be able to give the commands + +:: + + $ export PRESERVE=1 + $ python runtest.py test/packaging/sandbox-test.py + +, assuming Linux and a bash-like shell. + +The output should then look something like this:: + + 1/1 (100.00%) /usr/bin/python -tt test/packaging/sandbox-test.py + PASSED + Preserved directory /tmp/testcmd.4060.twlYNI + +and you see that the test files have been kept in the folder ``/tmp/testcmd.4060.twlYNI``, +where you can now copy them from to your new *fixture* folder. Then, in the test +script you simply remove all the tedious ``TestSCons.write()`` statements and +replace them by a single ``TestSCons.dir_fixture()``. + +Finally, you shouldn't forget to clean up and remove the temporary test directory. ``;)`` + +Test Infrastructure +=================== + +The test API is in ``testing/framework/TestSCons.py``. ``TestSCons`` is a subclass of +``TestCommon``, which is a subclass of ``TestCmd``; all those python files are +in ``testing/framework``. Start in ``testing/framework/TestCmd.py`` for the base API definitions, +like how to create files (``test.write()``) and run commands (``test.run()``). + +You want to use ``TestSCons`` for the end-to-end tests in ``test``, but ``TestCmd`` +for the unit tests in the ``src`` folder. + +The match functions work like this: + +TestSCons.match_re:: match each line with a RE + * Splits the lines into a list (unless they already are) + * splits the REs at newlines (unless already a list) and puts ^..$ around each + * then each RE must match each line. This means there must be as many REs as lines. + +TestSCons.match_re_dotall:: match all the lines against a single RE + * Joins the lines with newline (unless already a string) + * joins the REs with newline (unless it's a string) and puts ^..$ around the whole thing + * then whole thing must match with python re.DOTALL. + +Use them in a test like this:: + + test.run(..., match=TestSCons.match_re, ...) + +or:: + + test.must_match(..., match=TestSCons.match_re, ...) + +Avoiding Tests based on Tool existence +====================================== + +Here's an easy sample:: + + #!python + intelc = test.detect_tool('intelc', prog='icpc') + if not intelc: + test.skip_test("Could not load 'intelc' Tool; skipping test(s).\n") + +See ``testing/framework/TestSCons.py`` for the ``detect_tool`` method. It calls the tool's +``generate()`` method, and then looks for the given prog (tool name by default) in +``env['ENV']['PATH']``. + + diff --git a/timings/README.txt b/timings/README.txt index 3c0baff..9921208 100644 --- a/timings/README.txt +++ b/timings/README.txt @@ -5,8 +5,8 @@ This directory contains timing configurations for SCons. Each configuration exists in a subdirectory. The controlling script is named TimeSCons-run.py for the configuration. The TimeSCons-run.py scripts use TestSCons.TimeSCons, a subclass of TestSCons.TestSCons (both -defined in ../QMTest/TestSCons.py), to manage execution of the timing -runs. +defined in ../testing/framework/TestSCons.py), to manage execution of the +timing runs. Unlike the TestSCons.TestSCons base class, the TestSCons.TimeSCons subclass copies the contents of its containing directory to the temporary -- cgit v0.12 From aed736716d539a5f2df2b1478f40c4105fb1a915 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 29 Apr 2018 22:01:42 -0400 Subject: remove obsolete HOWTO dir.. ancient out of date information from when aegis was the source control tool used by SCons --- HOWTO/README | 24 -- HOWTO/change.txt | 61 ----- HOWTO/new-platform.txt | 74 ------ HOWTO/new-script.txt | 75 ------ HOWTO/new-tool.txt | 81 ------ HOWTO/release.txt | 656 ------------------------------------------------- 6 files changed, 971 deletions(-) delete mode 100644 HOWTO/README delete mode 100644 HOWTO/change.txt delete mode 100644 HOWTO/new-platform.txt delete mode 100644 HOWTO/new-script.txt delete mode 100644 HOWTO/new-tool.txt delete mode 100644 HOWTO/release.txt diff --git a/HOWTO/README b/HOWTO/README deleted file mode 100644 index e20df73..0000000 --- a/HOWTO/README +++ /dev/null @@ -1,24 +0,0 @@ -__COPYRIGHT__ - -Here you'll find plain text documentation of how to handle various SCons -project procedures. Files contained herein: - -change.txt - How changes are integrated, including generating and - distributing aedist change sets, and updating the CVS repository - on SourceForge. - -new-platform.txt - Steps to add a new Platform/*.py file. This is probably out - of date. - -new-script.txt - Steps to add a new script or utility (like scons and sconsign) - to what we ship. - -new-tool.txt - Steps to add a new Tool/*.py file. This is probably out of date. - -release.txt - Steps to go through when releasing a new version of SCons. - diff --git a/HOWTO/change.txt b/HOWTO/change.txt deleted file mode 100644 index f295003..0000000 --- a/HOWTO/change.txt +++ /dev/null @@ -1,61 +0,0 @@ -__COPYRIGHT__ - -Handling a change set: - - -- Start the change: - - aedb {cnum} [if it's initiated locally] - aedist -r [if it's a remote submission] - - -- Normal development cycle: - - aeb - aet - aet -bl - aet -reg [optional] - aed - aede - - -- As the reviewer: - - aerpass {cnum} - - -- As the integrator: - - aeib {cnum} - aeb - aet - aet -bl - aet -reg - aed - aeipass - - -- Distribute the change to scons-aedist: - - aedist -s -p scons.0.96 {cnum} > scons.0.96.C{cnum}.ae - pine -attach scons.0.96.C{cnum}.ae scons-aedist@lists.sourceforge.net - Subject: scons.0.96 - {SUMMARY} - Body: aegis -l -p scons.0.96 -c {cnum} cd - - rm scons.0.96.C{cnum}.ae - - [This will eventually be automated.] - - -- Update the aedist baseline on the web site: - - aedist -s -bl -p scons.0.96 > scons.0.96.ae - scp scons.0.96.ae stevenknight@scons.sourceforge.net:/home/groups/s/sc/scons/htdocs/scons.0.96.ae - rm scons.0.96.ae - - [This will eventually be automated.] - - -- Distribute the change to CVS: - - export CVS_RSH=ssh - ae2cvs -n -aegis -p scons.0.96 -c {cnum} -u ~/SCons/scons - ae2cvs -X -aegis -p scons.0.96 -c {cnum} -u ~/SCons/scons - - If you need the "ae2cvs" Perl script, you can find a copy - checked in to the bin/subdirectory. - - [This may eventually be automated.] diff --git a/HOWTO/new-platform.txt b/HOWTO/new-platform.txt deleted file mode 100644 index 8aa11ec..0000000 --- a/HOWTO/new-platform.txt +++ /dev/null @@ -1,74 +0,0 @@ -__COPYRIGHT__ - -Adding a new Platform to the SCons distribution: - - -- Add the following files (aenf): - - src/engine/SCons/Platform/xxx.py - - Use one of the other Platform files as a template. - - The tool_list() function should return the list of tools - that will be available through the default construction - Environment. - - The generate() function should set construction variables: - - ENV - OBJPREFIX - OBJSUFFIX - PROGPREFIX - PROGSUFFIX - LIBPREFIX - LIBSUFFIX - SHLIBPREFIX - SHLIBSUFFIX - LIBPREFIXES - LIBSUFFIXES - - -- Modify the following files (aecp): - - doc/man/scons.1 - - Add the new platform to the example of the platform - keyword when instantiating an Environment. - - Add the list of default tools returned by tool_list() - for this platform. - - rpm/scons.spec - - Add to the list at the bottom of the file: - - /usr/lib/scons/SCons/Platform/xxx.py - /usr/lib/scons/SCons/Platform/xxx.pyc - - [THIS LIST SHOULD BE GENERATED FROM MANIFEST.in, - AND WILL BE SOME DAY.] - - src/CHANGES.txt - - Add mention of the new Platform specification. - - src/engine/MANIFEST.in - - Add to the list: - - SCons/Platform/xxx.py - - src/engine/SCons/Platform/Platform/PlatformTests.py - - Copy and paste one of the other platform tests to verify - the ability to initialize an environment through a call - to the object returned by Platform('xxx') - - src/engine/SCons/Platform/__init__.py - - Add logic to platform_default() (if necessary) to return - the appropriate platform string. - - test/Platform.py - - Add the new platform to the SConstruct and SConscript - files. Add the expected output to the "expect" - variable. diff --git a/HOWTO/new-script.txt b/HOWTO/new-script.txt deleted file mode 100644 index f859c48..0000000 --- a/HOWTO/new-script.txt +++ /dev/null @@ -1,75 +0,0 @@ -__COPYRIGHT__ - -Steps for adding a new script/utility to the SCons packages. Assume -that you're creating a new man page, too. In the template steps -below, "newscript" is the name of the new script/utility being added. - - New files: - - doc/man/newscript.1 - - New man page; use an existing one as a template. - - src/script/newscript.py - - The new script itself. - - test/*.py - - Appropriate tests for the new script. - - Modified files: - - SConstruct - - In the "scons_script" package description: - - Add "newscript" : "newscript.py" to the "filemap" - dictionary. - - In the "scons" package description: - - Add "newscript.1" to the "files" list. - - Add "newscript.1" : "../doc/man/newscript.1" to the - "filemap" dictionary. - - Add "newscript" to the scripts[] list - - debian/rules - - Add: - - rm -f debian/scons/usr/bin/newscript - cp build/scripts/newscript debian/scons/user/bin/newscript - sed '1s|.*|#!/usr/bin/python2.1|' build/scripts/newscript > debian/scons/user/bin/newscript - chmod +x debian/scons/user/bin/newscript - - rm -f debian/scons/usr/share/man/man1/newscript.1 - cp newscript.1 debian/scons/user/share/man/man1/ - - doc/SConscript - - Add "newscript" to man_page_list[] - - gentoo/scons.ebuild.in - - Add "doman newscript.1" - - rpm/scons.spec.in - - Add "gzip -c newscript.1" > $RPM_BUILD_ROOT/usr/man/man1/newscript.1.gz - - Add "%doc /usr/man/man1/newscript.1.gz" - - src/CHANGES.txt - - Document the addition of the new script. - - src/script/MANIFEST.in - - Add a line: "newscript" - - src/setup.py - - Add "script/newscript" to the 'scripts' list. diff --git a/HOWTO/new-tool.txt b/HOWTO/new-tool.txt deleted file mode 100644 index 4297fe6..0000000 --- a/HOWTO/new-tool.txt +++ /dev/null @@ -1,81 +0,0 @@ -__COPYRIGHT__ - -Adding a new Tool to the SCons distribution: - - -- Add the following files (aenf): - - src/engine/SCons/Tool/xxx.py - - Use one of the other Tool files as a template. - - The tool should set and use construction variables: - - XXX (specific tool) - XXXCOM (the command line) - XXXFLAGS (flags for the command line) - - -- Modify the following files (aecp): - - doc/man/scons.1 - - Add the new tool to the default Platform lists - (if appropriate). - - If this creates object files from a new source - file type, add the new file suffixes. - - Document the new construction variables. - - rpm/scons.spec - - Add to the list at the bottom of the file: - - /usr/lib/scons/SCons/Tool/xxx.py - /usr/lib/scons/SCons/Tool/xxx.pyc - - [THIS LIST SHOULD BE GENERATED FROM MANIFEST.in, - AND WILL BE SOME DAY.] - - src/CHANGES.txt - - Add mention of the new Tool specification. - - src/engine/MANIFEST.in - - Add to the list: - - SCons/Tool/xxx.py - - src/engine/SCons/Defaults.py - - If there are any new Actions for the Tool, add them - here. - - src/engine/SCons/Platform/*.py - - Add the Tool to the list(s) returned by tool_list(). - - -- Add the following tests (aent): - - test/XXX.py - - Use one of the other tests as a template. - - The first part should be a platform-independent test - using dummy 'myxxx.py' utilities to test if $XXX is set. - - Remaining parts of the test can check to see if the - specific tool is installed on the system, and test - actually executing the tool. - - test/XXXFLAGS.py - - Use one of the other tests as a template. - - The first part should be a platform-independent test - using dummy 'myxxx.py' utilities to test if $XXXFLAGS is - set. - - Optionally, remaining parts of the test can check to see - if the specific tool is installed on the system, and - test actually executing the tool. diff --git a/HOWTO/release.txt b/HOWTO/release.txt deleted file mode 100644 index 5fbbfc7..0000000 --- a/HOWTO/release.txt +++ /dev/null @@ -1,656 +0,0 @@ -__COPYRIGHT__ - -This document covers how to prepare major releases of SCons--that is, -releases numbered with X.Y format, such as 0.93, 1.0, etc. - -If you need to prepare a specific subrelease (X.Y.Z, such as 0.94.1), -then see the document HOWTO/subrelease.txt. - -Things to do to release a new X.Y version of SCons: - - Prepare the describe-the-release section for the announcements - - summarize changes from src/CHANGES.txt - - template is below, search for "describe-the-release" - - send this out for review while you get the rest of the - release ready! - - Build and test candidate packages - - test on Linux - - test on Windows NT - - 1) tar zxf scons-src-{version}.tar.gz - cd scons-src-{version} - python runtest.py -a - - 2) tar zxf scons-{version}.tar.gz - cd scons-{version} - python setup.py install - cd scons-src-{version} - python runtest.py -a -X -x C:\Python20\scons.bat - - 3) scons-{verson}.win32.exe - cd scons-src-{version} - python runtest.py -a -X -x C:\Python20\scons.bat - - 4) mkdir temporary_directory - cd temporary_directory - tar zxf scons-local-{version}.tar.gz - cd scons-src-{version} - python runtest.py -a -x C:\temporary_directory\scons.py - - Check in any changes necessary to make everything work - - Update the user's guide - - sh bin/docdiff - - sh bin/docupdate - - END THE CURRENT DEVELOPMENT BRANCH - - ae_p scons.0 - - aede {96} - - aerpass {96} - - aeib {96} - - aed - - aeb - - aet - - aet -reg - - aeipass - - START THE NEW BRANCH FOR RELEASE - - aenbr -p scons.0 {97} - - aenc -p scons.0.{97} - - Call it something like, - "Initialize the new branch for release." - Cause = internal_enhancement. - Exempt it from all tests (*_exempt = true). - - ae_p scons.0.{97} - - aedb 100 - - aecd - - # Change the hard-coded package version numbers - # in the following files. - aecp README - vi README - - aecp SConstruct - vi SConstruct - - aecp QMTest/TestSCons.py - vi QMTest/TestSCons.py - - # Read through and update the README files if necessary - [optional] aecp README - [optional] vi README - - [optional] aecp src/README.txt - [optional] vi src/README.txt - - # Prepare src/CHANGES.txt - aecp src/CHANGES.txt - vi src/CHANGES.txt - - date -R the latest release - - should be current if this has been updated - as each change went in. - - # Prepare src/RELEASE.txt - aecp src/RELEASE.txt - vi src/RELEASE.txt - - date -R the latest release - - Read through and edit appropriately. - - Can probably keep most of the existing text - - Add any new known problems - - # Prepare debian/changelog - aecp debian/changelog - vi debian/changelog - - date -R the latest release - - # Now build and prepare the release itself. - aeb - - aed - - aet -reg - - aede - - etc. - - - - Read through the FAQ for any updates - - - - Upload the packages to the SourceForge incoming FTP: - - ftp upload.sourceforge.net - anonymous - - cd incoming - bin - put scons-0.{97}-1.noarch.rpm - put scons-0.{97}-1.src.rpm - put scons-0.{97}.tar.gz - put scons-0.{97}.win32.exe - put scons-0.{97}.zip - put scons-local-0.{97}.tar.gz - put scons-local-0.{97}.zip - put scons-src-0.{97}.tar.gz - put scons-src-0.{97}.zip - put scons_0.{97}-1_all.deb - - Create the new release at the SourceForge project page: - - Pull down the "Admin" menu and select "File Releases" - - Package Name: scons - - => Add Release - - New release name: 0.{97} - - Upload the RELEASE.txt file. - - Upload the CHANGES.txt file. - - Check the "Preserve my pre-formatted text." box (IMPORTANT!) - - Click "Submit/Refresh" (IMPORTANT!) - - Check the SCons files you uploaded - - Click "Add Files and/or Refresh View" - - Edit the file info: - - scons-0.{97}-1.noarch.rpm Any .rpm - scons-0.{97}-1.src.rpm Any Source .rpm - scons-0.{97}.tar.gz Any .gz - scons-0.{97}.win32.exe i386 .exe (32-bit Windows) - scons-0.{97}.zip Any .zip - scons_0.{97}-1_all.deb Any .deb - - Click "Update/Refresh" for each file; this must be done - one at a time. - - Check "I'm sure." and click "Send Notice" in the Email - Release Notice section. - - - Pull down the "Admin" menu and select "File Releases" - - Package Name: scons-local - - => Add Release - - New release name: 0.{97} - - Upload the RELEASE.txt file. - - Upload the CHANGES.txt file. - - Check the "Preserve my pre-formatted text." box (IMPORTANT!) - - Click "Submit/Refresh" (IMPORTANT!) - - Check the SCons files you uploaded - - Click "Add Files and/or Refresh View" - - Edit the file info: - - scons-local-0.{97}.tar.gz Any .gz - scons-local-0.{97}.zip Any .zip - - Click "Update/Refresh" for each file; this must be done - one at a time. - - Check "I'm sure." and click "Send Notice" in the Email - Release Notice section. - - - Pull down the "Admin" menu and select "File Releases" - - Package Name: scons-src - - => Add Release - - New release name: 0.{97} - - Upload the RELEASE.txt file. - - Upload the CHANGES.txt file. - - Check the "Preserve my pre-formatted text." box (IMPORTANT!) - - Click "Submit/Refresh" (IMPORTANT!) - - Check the SCons files you uploaded - - Click "Add Files and/or Refresh View" - - Edit the file info: - - scons-src-0.{97}.tar.gz Any .gz - scons-src-0.{97}.zip Any .zip - - Click "Update/Refresh" for each file; this must be done - one at a time. - - Check "I'm sure." and click "Send Notice" in the Email - Release Notice section. - - - Hide release 0.{95} at the SourceForge download page: - - Pull down the "Admin" menu and select "File Releases" - - Package Name: scons - - => Edit Releases - - Release Name: 0.{95} - - => Edit This Release - - Status: => Hidden - - Click Submit/Refresh - - - Pull down the "Admin" menu and select "File Releases" - - Package Name: scons-local - - => Edit Releases - - Release Name: 0.{95} - - => Edit This Release - - Status: => Hidden - - Click Submit/Refresh - - - Pull down the "Admin" menu and select "File Releases" - - Package Name: scons-src - - => Edit Releases - - Release Name: 0.{95} - - => Edit This Release - - Status: => Hidden - - Click Submit/Refresh - - - - Add a new release for 0.{97} in the Issue Tracker at tigris.org: - - Click "Issue Tracker" on the left-hand nav bar - - Click "Configuration options" - - Click "Add/edit components" - - Under "scons" - To the right of "Add ..." - Click "Version" - - At the bottom of the list click "Add" - - Fill in the "Version:" box with 0.{97} - - Check "Add this version to *all* components." - - Click the "Add" button - - - - Update the scons.org web site: - - svn co http://scons.tigris.org/svn/scons/scons.org - - cd scons.org - - CHANGES.txt: copy new version from built source tree - - download.php: new version number - - includes/templates.php: - update $latestrelease - - includes/versions.php: - update $stablerelease and/or $latestrelease - add new version number to $docversions[], - IMPORTANT: SHIFT $docversions[] INDEX NUMBERS :-( - - index.php: announcement on the home page - remove out-of-date announcements - - news-raw.xhtml: add announcement to list (dup from home page) - - RELEASE.txt: copy new version from built source tree - - mkdir doc/0.{97} - - (cd doc/0.{97} && tar zxf scons-doc-0.{97}.tar.gz) - - svn add doc/0.{97} - - svn commit - - ssh -l scons manam.pair.com - - cd public_html - - mkdir new - - svn co http://scons.tigris.org/svn/scons/scons.org new - - mv production previous && mv new production - - [point your browser to http://www.scons.org/] - - - Update the project pages at tigris.org: - - svn co http://scons.tigris.org/svn/scons/trunk - - cd trunk - - www/project_highlights.html - - www/roadmap.html - - svn commit - - - - - Test downloading from the SourceForge project page - - You may need to wait a good bit; they seem to update - this on half-hour cycles. - - - Test downloading from the web site download page - - - - Add news item to the SourceForge project page - - Pull down "Project => News" - - Click "Submit" - - Fill in the "Subject:" box - - Cut-and-paste the announcement text into the "Details:" box - - Click "submit" - - - - Add news item to the tigris.org project page - - Click "Announcements" - - Click "Add new announcement" - - Double-check the date (probably already set) - - Fill in the "Headline" box - - Fill in the "Body" box (probably short) - - Click "Add new announcement" - - - - Announce to the following mailing lists (template below): - - scons-announce@lists.sourceforge.net - scons-users@lists.sourceforge.net - scons-devel@lists.sourceforge.net - - [right away] - - python-announce@python.org - - [right away, it's moderated and will take - some time to get through] - - linux-announce@news.ornl.gov - - [right away, it's moderated and will take - some time to get through] - - [optional] cons-discuss@gnu.org - - [only if it's a really big announcement, - I said we wouldn't bug this list] - - python-list@python.org - - [wait until business hours so the announcement - hits mailboxes while U.S. workers are active] - - Notify Gentoo Linux of the update - - For now, we will do this by entering a bug report, and - attaching the files in build/gentoo to the report. Go - to: - - http://bugs.gentoo.org/ - - This requires an account (based on your email address) - and a certain amount of Bugzilla-based navigation, - but nothing that's too difficult. - - This is just my best stab at a process that will work - for Gentoo. This process may change if the Gentoo - developers come back and want something submitted in - some other form. - - Notify www.cmtoday.com/contribute.html - - [This guy wants an announcement no more frequently than - once a month, so save it for a future release if it's - been too soon since the previous one.] - - Notify freshmeat.net - - [Wait until the morning so the announcement hits the - main freshmeat.net page while people in the U.S. are - awake and working] - - - - Checkin another change to prepare for development on this branch. - - # Prep the following files to track the changes - # made during the next development cycle - aecp src/CHANGES.txt src/RELEASE.txt - vi src/CHANGES.txt src/RELEASE.txt - - # Optionally, update release numbers in the following: - [optional] aecp HOWTO/change.txt - [optional] vi HOWTO/change.txt - - [optional] aecp HOWTO/release.txt - [optional] vi HOWTO/release.txt - - - - -======================= -Template describe-the-release section: - -IMPORTANT: Release 0.95 contains the following interface changes: - - - XXX - - See the release notes for more information about these changes. - -This release adds the following features: - - - XXX - -This release enhances the following existing features: - - - XXX - -The following fixes have been added: - - - XXX - -Performance has been improved as follows: - - - XXX - -The following changes have been made to the SCons packaging: - - - XXX - -The documentation has been improved: - - - XXX -======================= -Template scons-devel announcement: - -SConspirators-- - -SCons beta release 0.95 is now available for download. - -XXX Template describe-the-release section goes here XXX - -Special thanks to XXX, XXX, and XXX for their contributions to this -release. - - --SK -======================= -Template scons-users + scons-announce announcement: - -Version 0.95 of SCons has been released and is available for download -from the SCons web site: - - http://www.scons.org/ - -Or through the download link at the SCons project page at SourceForge: - - http://sourceforge.net/projects/scons/ - -RPM and Debian packages and a Win32 installer are all available, in -addition to the traditional .tar.gz and .zip files. - - -WHAT'S NEW IN THIS RELEASE? - -XXX Template describe-the-release section goes here XXX - - -ACKNOWLEDGEMENTS - -Special thanks to XXX, XXX, and XXX for their contributions to this -release. - -On behalf of the SCons team, - - --SK -======================= -Template python-announce, linux-announce and python-list announcement: - -SCons is a software construction tool (build tool, or make tool) written -in Python. It is based on the design which won the Software Carpentry -build tool competition in August 2000. - -Version 0.95 of SCons has been released and is available for download -from the SCons web site: - - http://www.scons.org/ - -Or through the download link at the SCons project page at SourceForge: - - http://sourceforge.net/projects/scons/ - -RPM and Debian packages and a Win32 installer are all available, in -addition to the traditional .tar.gz and .zip files. - - -WHAT'S NEW IN THIS RELEASE? - -XXX Template describe-the-release section goes here XXX - - -ABOUT SCONS - -Distinctive features of SCons include: - - - a global view of all dependencies; no multiple passes to get - everything built properly - - configuration files are Python scripts, allowing the full use of a - real scripting language to solve difficult build problems - - a modular architecture allows the SCons Build Engine to be - embedded in other Python software - - the ability to scan files for implicit dependencies (#include files); - - improved parallel build (-j) support that provides consistent - build speedup regardless of source tree layout - - use of MD5 signatures to decide if a file has really changed; no - need to "touch" files to fool make that something is up-to-date - - easily extensible through user-defined Builder and Scanner objects - - build actions can be Python code, as well as external commands - -An scons-users mailing list is available for those interested in getting -started using SCons. You can subscribe at: - - http://lists.sourceforge.net/lists/listinfo/scons-users - -Alternatively, we invite you to subscribe to the low-volume -scons-announce mailing list to receive notification when new versions of -SCons become available: - - http://lists.sourceforge.net/lists/listinfo/scons-announce - - -ACKNOWLEDGEMENTS - -Special thanks to XXX, XXX, and XXX for their contributions to this -release. - -On behalf of the SCons team, - - --SK -- cgit v0.12 From df92a34720a8511f63a12abcd7e0ca2deeeca4d1 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Mon, 30 Apr 2018 10:11:01 -0400 Subject: remove obsolete directory www. Was leftover from when SVN was hosted on tigris.org. This directory affected some project pages on tigris.org --- www/bug-submission.html | 234 -------------- .../Administrivia/snippets/HtmlSnippet1.html | 1 - .../Administrivia/snippets/description.txt | 1 - .../Administrivia/snippets/page.xml | 10 - www/cn-project-pages/snippets/page.xml | 16 - www/cn-project-pages/structure.xml | 10 - www/favicon.ico | Bin 1406 -> 0 bytes www/feature-request.html | 99 ------ www/gen_sched_table.py | 51 --- www/index.html | 288 ----------------- www/patch-submission.html | 355 --------------------- www/project_highlights.html | 24 -- www/project_issues.html | 123 ------- www/project_tools.html | 41 --- www/roadmap.html | 173 ---------- www/schedule | 19 -- 16 files changed, 1445 deletions(-) delete mode 100644 www/bug-submission.html delete mode 100644 www/cn-project-pages/Administrivia/snippets/HtmlSnippet1.html delete mode 100644 www/cn-project-pages/Administrivia/snippets/description.txt delete mode 100644 www/cn-project-pages/Administrivia/snippets/page.xml delete mode 100644 www/cn-project-pages/snippets/page.xml delete mode 100644 www/cn-project-pages/structure.xml delete mode 100644 www/favicon.ico delete mode 100644 www/feature-request.html delete mode 100755 www/gen_sched_table.py delete mode 100644 www/index.html delete mode 100644 www/patch-submission.html delete mode 100644 www/project_highlights.html delete mode 100644 www/project_issues.html delete mode 100644 www/project_tools.html delete mode 100644 www/roadmap.html delete mode 100644 www/schedule diff --git a/www/bug-submission.html b/www/bug-submission.html deleted file mode 100644 index abc855b..0000000 --- a/www/bug-submission.html +++ /dev/null @@ -1,234 +0,0 @@ - - -Bug Submission - - - -
    -

    scons
    Bug Submission

    - -

    -You must now -log in -to a tigris.org account -before submitting a new bug report! -

    - -

    -Bugs should be reported at the -"Enter Issue" page. -Please follow the submission guidelines below -to make sure your bug report contains the necessary information. -A more detailed set of submission steps -can be found below. -

    - -

    -The above URL is set up for reporting a bug in SCons itself. -If you are reporting a problem in some other aspect of the SCons Project -(such as the documentation, or any of the web pages), -you must change the Subcomponent field of the submission form -to some other appropriate value. -

    - -
    - -
    - -

    Guidelines for a Useful Bug Report

    - -

    -Your bug will be much more likely to get diagnosed and fixed -if you supply all the necessary information to make it easy to do so. -

    - -
      -
    • - -Log in -to your tigris.org account before submitting a bug report - -

      -If you do not already have a tigris.org account, -register for one at -http://www.tigris.org/servlets/Join. -

      -

      -We no longer accept anonymous bug reports, -due to spambot abuse of the open-door policy. -

      -
    • -
    • -Specify the version of SCons in which you observed the problem -

      -This helps avoid wasted time trying to pinpoint the version, -and also allows us to confirm if a later released version -has already fixed your problem. -

      -
    • -
    • -Provide SConscript files or other configuration that reproduce the problem -

      -If you can, simplify the configuration to just the -minimal subset that demonstrates the problem. -It's much harder to diagnose a problem -if the incorrect behavor is due to -one particular item out of a thousand -in a large configuration. -

      -

      -That said, it's most convenient if you can provide -an actual configuration (set of SConscript files -and/or other input files) -that can be downloaded and run to demonstrate the bug. -The easiest way is to attach a .tar.gz or .zip file -to the bug report. -Note that the tigris.org Issue Tracker -doesn't let you attach a file like this -when you initially submit the report. -You must first create the bug report, -and then attach a file to it as a separate step. -See below for the detailed steps. -

      -

      -If your problem is evident from a few specific SConscript lines, -it's perfectly acceptable just to -paste the lines into the Description field of the bug report. -

      -
    • -
    • -Describe specifically the incorrect behavor you observed -

      -It's best if you can cut and paste the output from SCons, -especially any error messages. -Otherwise, -Vague descriptions like, -"SCons errors out," or "Product XYZ doesn't compile" -are extremely difficult to diagnose, -because the different installed tools on someone else's system -may cause SCons to behave differently -and not demonstrate your bug. -

      -
    • -
    • -Describe what you expected to happen -

      -This isn't always obvious, especially if the -bug does not involve an SCons failure or error message. -Describing the behavior you expected -helps speed up the diagnosis. -

      -
    • -
    - -

    Steps for Submitting a Bug Report

    - -

    -The following guides you step-by-step through the -process of submitting a new SCons bug report. -

    - -

    -NOTE: Creating a bug report with an attached file or files -(such as a .tar.gz or .zip file containing a sample configuration) -is a two-step process in the tigris.org Issue Tracker. -You must first create the bug report, -and then attach the file(s) in a separate step, -as described below. -

    - -
      -
    • -Log in at tigris.org -

      -If you do not already have a tigris.org account, -register for one at -http://www.tigris.org/servlets/Join. -

      -

      -We no longer accept anonymous bug reports, -due to spambot abuse of the open-door policy. -

      -
    • -
    • -Go to the -"Enter issue" page - -

      -By default, the "scons" subcomponent is selected; -if this bug is for a different subcomponent, select that instead. -

      -
    • -
    • -Specify the version of SCons in which you found the bug -

      -

      -
    • -
    • -Specify the Subcomponent (if the bug is not in SCons itself) -

      -The URL two steps above assumes that you're reporting -a bug in the behavior of SCons itself. -If you're reporting a problem in some other aspect of the SCons Project -(such as the documentation, or the packaging), -please change the Subcomponent field to reflect that. -

      -
    • -
    • -Specify the Platform and OS -

      -The Platform field is less important here -(SCons doesn't have many behavioral difference -due to different hardware platforms) -but the OS field is important. -

      -
    • -
    • -Fill in a good Summary line describing the bug -

      -This line is what shows up in summary reports, -so it should be descriptive but not too long. -Avoid overly-general things like "SCons error," etc. -

      -
    • -
    • -Fill in the Description field -

      -This is where you should go into detail -about the configuration, -the exact error you see, -what you expected to happen, etc. -When in doubt, include more information rather than less. -

      -
    • -
    • -Press the "Submit issue" to submit your report -

      -You will now receive a Posting issue page -that gives you the number of the issue you submitted. -

      -
    • -
    • -If you have a .tar.gz, .zip or other file to attach: -
        -
      • -Click the "Attach a file to this issue" link -
      • -
      • -Fill in the "File" field with the path to the file you want to upload -(You can also do this through the Browse... button.) -
      • -
      • -Fill in the Description field -
      • -
      • -Click the "Submit" button -
      • -
      -
    • -
    - -
    - - - diff --git a/www/cn-project-pages/Administrivia/snippets/HtmlSnippet1.html b/www/cn-project-pages/Administrivia/snippets/HtmlSnippet1.html deleted file mode 100644 index a311062..0000000 --- a/www/cn-project-pages/Administrivia/snippets/HtmlSnippet1.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/www/cn-project-pages/Administrivia/snippets/description.txt b/www/cn-project-pages/Administrivia/snippets/description.txt deleted file mode 100644 index 08c477b..0000000 --- a/www/cn-project-pages/Administrivia/snippets/description.txt +++ /dev/null @@ -1 +0,0 @@ -Administrative information and links \ No newline at end of file diff --git a/www/cn-project-pages/Administrivia/snippets/page.xml b/www/cn-project-pages/Administrivia/snippets/page.xml deleted file mode 100644 index 7086b8d..0000000 --- a/www/cn-project-pages/Administrivia/snippets/page.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Administrative links - HtmlSnippet1.html - - - - diff --git a/www/cn-project-pages/snippets/page.xml b/www/cn-project-pages/snippets/page.xml deleted file mode 100644 index 005b144..0000000 --- a/www/cn-project-pages/snippets/page.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - </component> - <component visibility="1" type="Html" order="2"> - <title localize="false" /> - <filename>index.html</filename> - </component> - <component visibility="1" type="Subproject" order="3"> - <title localize="true">Subprojects - - - - diff --git a/www/cn-project-pages/structure.xml b/www/cn-project-pages/structure.xml deleted file mode 100644 index 2c1f3bb..0000000 --- a/www/cn-project-pages/structure.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - g2uyOi - Administrivia - Administrivia - 1 - - - diff --git a/www/favicon.ico b/www/favicon.ico deleted file mode 100644 index 1541e5b..0000000 Binary files a/www/favicon.ico and /dev/null differ diff --git a/www/feature-request.html b/www/feature-request.html deleted file mode 100644 index e644a16..0000000 --- a/www/feature-request.html +++ /dev/null @@ -1,99 +0,0 @@ - - -Feature Requests - - - -
    -

    scons
    Feature Requests

    -
    - -

    -You must now -log in -to a tigris.org account -before submitting a feature request! -

    - -

    -Feature requests should be submitted to the -"Enter Issue" page. -A more detailed set of submission steps -can be found below. -

    - -

    -The "Enter Issue" links on this page, -by default, create a FEATURE request, -a request for completely new SCons functionality. -If your request is for modified behavior -of an already-existing SCons feature, -you may wish to change the Issue type -to an ENHANCEMENT request. -If you're not sure, leave the issue as a FEATURE request; -it can be reclassified in the future, if necessary. - - -

    - -

    Steps for Submitting a Feature Request

    - -

    -The following guides you step-by-step through the -process of submitting a feature request. -

    - -
      -
    • -Log in at tigris.org -

      -If you do not already have a tigris.org account, -register for one at -http://www.tigris.org/servlets/Join. -

      -

      -We no longer accept anonymous feature requests, -due to spambot abuse of the open-door policy. -

      -
    • -
    • -Go to the -"Enter issue" page - -

      -By default, the "scons" subcomponent is selected; -if this bug is for a different subcomponent, select that instead. -

      -
    • -
    • -Fill in a good Summary line describing the feature you're requesting -

      -This line is what shows up in summary reports, -so it should be descriptive but not too long. -

      -
    • -
    • -Fill in the Description field -

      -This is where you should go into detail -about what you'd like to see SCons do differently. -When in doubt, include more information rather than less. -

      -
    • -
    • -Press the "Submit issue" to submit your request -

      -You will now receive a Posting issue page -that gives you the number of the issue you submitted. -If you want to attach a file to your feature request, -you can do so by clicking the -Attach a file to this issue -link and using that page to upload a file. -

      -
    • -
    - -
    - - - diff --git a/www/gen_sched_table.py b/www/gen_sched_table.py deleted file mode 100755 index 85b1b81..0000000 --- a/www/gen_sched_table.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function - -import sys -import datetime - -months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - -print('') -def row(*cells, **kw): - td = kw.get('tr','td') - print(' ') - for cell in cells: - print(' <%s>%s' % (td,cell,td)) - print(' ') -row('Estimated date', 'Type', 'Comments', tr = 'th') - -if len(sys.argv) > 1: - f = open(sys.argv[1]) -else: f = open('schedule') -now = None -current = 'UNKNOWN' -for line in f: - if line[0] == '#': continue # comment - if line[0] == '=': - date,current = line[1:].strip().split(None, 1) - now = datetime.date(*tuple([int(i) for i in date.split('-')])) - continue - if line[0] == '+': - incr,type,desc = line[1:].strip().split(None,2) - now = now + datetime.timedelta(int(incr)) - else: - print('dunna understand code', line[0]) - sys.exit(1) - #name = current + '.d' + str(now).replace('-','') - date = '%s-%s-%s' % (now.day,months[now.month-1],now.year) - if type == 'ck': - category = 'Ckpt' - elif type == 'rc': - category = 'RC' - else: - category = current = type - row(date, category, desc) -print('
    ') - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/www/index.html b/www/index.html deleted file mode 100644 index 203435d..0000000 --- a/www/index.html +++ /dev/null @@ -1,288 +0,0 @@ - - - - - -
    - -

    What is SCons?

    - -

    SCons is a next-generation, -cross-platform, build tool. -Think of SCons as an improved -substitute for the classic -Make utility -with integrated functionality -similar to autoconf/automake -and compiler caches such as ccache. -

    - -

    -Unlike build tools that invent their own mini-language -or wedge a scripting language onto some other -configuration file syntax, -SCons configuration files -are actually Python scripts. -The ability to script your build -gives you a tremendous amount of flexibility -to solve complicated build problems -in surprisingly small amounts of maintainable code. -

    - -

    -In short, SCons is an easier, more reliable -and more flexible way to build software. -

    - - - -

    SCons Features

    - -
      - -
    • -Configuration files are Python scripts -

      -This provides much more flexibility for solving -difficult build problems -than traditional build tools. -

      -
    • - -
    • -Reliable, automatic dependency analysis -

      -C, C++ and Fortran are scanned for dependencies, -eliminating the need for a separate make depend step -or a make clean to get all of the dependencies. -Avoids the time waste from debugging phantom problems -that mysteriously disappear after you -make clean; make. -Easily extended to scan for other languages or file types. -

      -
    • - -
    • -Built-in support for multiple languages -

      -C, C++, D, Java, Fortran, Yacc, Lex, Qt and SWIG. -Can also build TeX and LaTeX documents. -Easily extended for other languages or file types. -

      -
    • - -
    • -Cross-platform -

      -Known to work on Linux, -other POSIX systems (AIX, *BSD, HP/UX, IRIX, Solaris), -Windows (NT, 2000, XP), -Mac OS X, -and OS/2. -

      -
    • - -
    • -Fetch files from SCM systems or central directory trees -

      -Built-in support for SCCS, RCS, CVS, BitKeeper and Perforce. -On-disk directory trees can be searched for source files -or pre-built target files. -

      -
    • - -
    • -Support for Microsoft Visual Studio .NET and 2005 -

      -Generates .dsp and .dsw files, -or .sln and .vcproj files, -from the same build configuration used to build on all platforms. -Allows Windows developers to do all the productive -point-and-click debugging they're used to -without having to maintain a separate build configuration -just for Windows. -

      -
    • - -
    • -Reliable detection of file changes using MD5 signatures -

      -Use of traditional file timestamps instead of MD5 can be configured. -

      -
    • - -
    • -Parallel builds -

      -Keeps up to N jobs running simultaneously regardless -of directory hierarchy. -

      -
    • - -
    • -Global view of dependencies -

      -Simplifies builds by eliminating multiple passes -or reording targets to build everything correctly. -

      -
    • - -
    • -Multi-platform configuration (like Autoconf) -

      -Support for finding #include files, -libraries, functions and typedef declarations. -

      -
    • - -
    • -Shared built-file cache -

      -Speeds up multiple builds by allowing developers -to share pre-built targets -(like ccache, but for any type of target file, -not just C/C++ compilation). -

      -
    • - -
    - - - -

    Future

    - -See the Roadmap page. - -
    - - - diff --git a/www/patch-submission.html b/www/patch-submission.html deleted file mode 100644 index a2d9b95..0000000 --- a/www/patch-submission.html +++ /dev/null @@ -1,355 +0,0 @@ - - -Patch Submission - - - -
    -

    scons
    Patch Submission

    -
    - -

    -You must now -log in -to a tigris.org account -before submitting a patch! -

    - -

    -Patches should be submitted to the -"Enter Issue" page. -Please follow the submission guidelines below -to make sure your patch contains the necessary information. -A more detailed set of submission steps -can be found below. -

    - - - -
    - -

    Guidelines for Patch Submission

    - -

    -To try to maintain and improve the quality of SCons releases, -we have some pretty high standards for the quality of patches -that make it into the SCons code base. -This list of guidelines describes how to make it as -easy as possible for your patch to be accepted for integration. -We're still interested in your code -even if you don't follow all of these guidelines, -but then your patch will more than likely sit in the queue -until someone else has time to supply all of the -necessary missing items. -

    - -
      - -
    • - -Please -log in -to your tigris.org account before submitting any patch - -

      -If you do not already have a tigris.org account, -register for one at -http://www.tigris.org/servlets/Join. -

      -

      -We no longer accept anonymous patches, -due to spambot abuse of the open-door policy. -

      -
    • - -
    • -If your patch is extensive, discuss it first on the -scons-dev@scons.org -mailing list - -

      -In fact, for extensive changes, it's a good idea to have this discusssion -before you invest too much time in coding. -It's possible that your idea overlaps with something else -already in the works, -or that your idea is unlikely to be accepted -because it would conflict with planned directions for SCons. -It's much better to find that out, -or get advice on acceptable design choices. -before you've spent a lot of time polishing code -that will be rejected because it doesn't fit -plans for the architecture. -

      -
    • - -
    • -It's better to submit multiple patches with separate bits of functionality than a big patch containing lots of changes -

      -Big, intertwined sets of changes -increase the chances of unintended side effects -that could cause the entire patch to be rejected. -If you submit separate functional changes in separate patches, -those change that meet all the criteria can -still be integrated even -though other pieces might be held up for one reason or another. -

      -
    • - -
    • -Submit your patch in diff -u or diff -c format -

      -In particular, do not submit whole source files, -or diff output without any kind of context information. -It's much more difficult to integrate whole source files -or plain diff output with other changes to -the SCons code base, -especially other changes that might be integrated -after you've submitted your patch. -

      -
    • - -
    • -Your patch must include test case(s) before it can be integrated! -

      -THIS IS THE SINGLE MOST COMMON REASON FOR DELAYS IN INTEGRATING PATCHES -AND THE SINGLE MOST IMPORTANT THING YOU CAN DO TO INCREASE THE -CHANCES OF YOUR PATCH BEING INTEGRATED QUICKLY. -

      -

      -The SCons development methodology requires -that each change be accompanied by one or more -new or modified test cases -that get added to our extensive regression test suite. -This is to make sure that the behavior added by your patch -doesn't get inadvertently broken by other changes in the future. -Patches that fix bugs should contain at least one test case -that demonstrates the behavior being fixed by the patch. -For example, if you're fixing a configuration that causes -SCons to exit with an error and a stack trace, -the test case should trigger that stack trace -when run against the current code. -Patches that add new features or enhancements -should contain test cases that use -the new behavior being added to SCons. -

      -

      -You can do any of the following to supply -test cases with your patch: -

      -
        -
      • -Include actual new or modified SCons test scripts in your patch -

        -This is the best option because it's the easiest to integrate, -and therefore maximizes the chances of your patch being accepted quickly. -(Note that, yes, there's a curve to learning how to -write test scripts in the SCons testing harness. -We're working on documentation to deal with that.) -

        -
      • -
      • -Include a .tar.gz or .zip file containing test configurations -

        -If you can't quite figure out how to deal with the SCons test scripts, -the next best option is to include with your patch an archive file -containing one or more actual test configurations -(SConscript files, input files, etc.). -It will be relatively straightforward for someone integrating your patch, -and who's presumably familiar with the SCons testing harness, -to turn this into an appropriate test script. -Be sure to include a description of how to run your recommended test scenario, -or a script for doing so. -

        -
      • -
      • -Describe how to go about testing the patch -

        -If you really can't cook up a test configuration to include with the patch, -the lowest-common-denominator approach is to just describe -how to go about testing the patch. -Be as specific as possible, -even if you think it should be obvious -how to test the patch. -It might be clear to you while you're writing the code, -but it will still take someone else time -to make sure they understand your intent -and work out the details of how to set up an appropriate case. -The point is you're trying to use your existing knowledge -of the bug being fixed or new feature being added -to make the process of integrating your patch as -simple and quick as possible, -thereby increasing the chance of your patch making it -into the SCons code base. -

        -
      • -
      -

      -If you don't supply any sort of testing -information with your patch, -well, you're still welcome to submit the code. -Just be aware that the patch will likely stay -in the queue until someone has time to reverse-engineer -a test case. -

      -
    • - -
    • -Your patch should not break any existing tests -

      -This almost sounds like it should go without saying, -but the reason we put so much emphasis on test cases -is so that we can make sure functionality doesn't break. -Your patch will almost certainly be run through the -the complete set of checked-in test scripts, -and if any of them break, -your patch will either be rejected outright -or delayed while someone else figures out how to fix it -(or the tests) so that everything works correctly. -You should, of course, avoid this by running your patch -against the regression tests and fixing any problems -before submitting your patch. -If you run your patch against against the regression tests -but can't figure out how to fix all the cases, -the best bet would be to ask the -scons-dev@scons.org -mailing list. -

      -
    • - -
    • -Your patch should include documentation changes -

      -We also insist that changes to the SCons code base -be accompanied by appropriate changes to the documentation. -In practice, right now we make sure the man page is up to date, -and updates to the User's Guide often lag. -

      -

      -Similar to the guidelines above for testing, -if you don't submit changes to the actual man page with your patch, -it's helpful if you at least provide -some suggested text describing your change. -Even if the actual words get rewritten -(usually to make the style consistent with the rest of the man page), -taking the time to provide this -makes the integration easier because -the person integrating the patch doesn't have -to reverse-engineer the intent -of your change to figure out how to describe it. -

      -
    • - -
    - -

    Steps for Submitting a Patch

    - -

    -The following guides you step-by-step through the -process of submitting a patch to SCons. -

    - -

    -NOTE: Attaching a file or files -(such as a .tar.gz or .zip file containing your patch) -is a two-step process in the tigris.org Issue Tracker. -You must first create the patch issue in the Tracker, -and then attach the file(s) in a separate step, -as described below. -

    - -
      - -
    • -Log in at tigris.org -

      -If you do not already have a tigris.org account, -register for one at -http://www.tigris.org/servlets/Join. -

      -

      -We no longer accept anonymous bug reports, -due to spambot abuse of the open-door policy. -

      -
    • - -
    • -Go to the -"Enter issue" page - -

      -By default, the "scons" subcomponent is selected; -if this bug is for a different subcomponent, select that instead. -

      -
    • - -
    • -Specify the version of SCons that you used as a baseline -

      -You can leave this -unspecified-, -in which case the assumption will be that you started with -the code checked in to our Subversion repository -at the time you opened the issue. -

      -
    • - -
    • -Fill in a good Summary line describing the patch -

      -This line is what shows up in summary reports, -so it should be descriptive but not too long. -Avoid overly-general things like "SCons error," etc. -

      -
    • - -
    • -Fill in the Description field -

      -This is where you should describe -the nature of your patch: -the exact error it fixes, -the feature that it adds, -how to go about testing it, -etc. -When in doubt, include more information rather than less. -

      -
    • - -
    • -Press the "Submit issue" to submit your report -

      -You will now receive a Posting issue page -that gives you the number of the issue you submitted. -

      -
    • - -
    • -Click the "Attach a file to this issue" link -

      -

      -
    • - -
    • -Fill in the "File" field with the path to the patch file you want to upload -

      -(You can also do this through the Browse... button.) -

      -
    • - -
    • -Fill in the Description field -

      -

      -
    • - -
    • -Click the "Submit" button to attach your patch file -

      -

      -
    • - -
    - -
    - - - diff --git a/www/project_highlights.html b/www/project_highlights.html deleted file mode 100644 index 98f00bc..0000000 --- a/www/project_highlights.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -

    -09 Sep 2011: -Release 2.1.0 is now available at the -download page. -

    - - -

    -15 Aug 2010: -Release 2.0.1 is now available at the -download page. -

    - - - - - - diff --git a/www/project_issues.html b/www/project_issues.html deleted file mode 100644 index dc28ca8..0000000 --- a/www/project_issues.html +++ /dev/null @@ -1,123 +0,0 @@ -
    -

    scons
    Issue Tracker -

    -
    - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Query database - Search for issues and defects here or find a specific issue. Always search first before reporting an issue to avoid duplication. -
    -

    - Issue # - -

    -
    -

    - Common queries: -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    All Open Issues
    • Bug Reports:
    All Open Non-Open
    • Enhancement:
    All Open Non-Open
    • Feature Requests:
    All Open Non-Open
    • Patches:
    All Open Non-Open
    • Tasks:
    All Open Non-Open
    -
    Enter an issue - - To enter an issue, you must first be a project member and know the component you want to report on. - - - -

    - -Enter: -

    - -
    My issues - View both active issues assigned to you and those that you have entered. -
    My preferences - View and edit your Issue Tracker user settings. -
    Reports - Generate and view issue tracking reports. -
    Configuration options - - Add, view and edit Issue Tracker configuration parameters, including project member permissions, issue tracking groups, project components and subcomponents, etc. -
    -
    diff --git a/www/project_tools.html b/www/project_tools.html deleted file mode 100644 index cf80234..0000000 --- a/www/project_tools.html +++ /dev/null @@ -1,41 +0,0 @@ - -
  • SCons Home Page
  • -
  • SCons Wiki
  • -
    - -
    -
  • Project Membership
  • -
  • Announcements
  • -
  • Discussion lists/fora
  • - -
  • Documents & files
  • -
  • Browse source code
  • -
  • Project metrics
  • -
    - -
    -
  • - -Issue Tracker - -
  • -
    - -
    -
  • Roadmap
  • diff --git a/www/roadmap.html b/www/roadmap.html deleted file mode 100644 index 333d0fd..0000000 --- a/www/roadmap.html +++ /dev/null @@ -1,173 +0,0 @@ - - -scons: Release Roadmap - - - -
    -

    Release Roadmap

    -
    - -
    - -

    Current Releases

    - -

    -The current stable release is 2.1.0, released 09 Sep 2011. -The latest 2.1.x release is 2.1.0, released 09 Sep 2011. -

    - -

    -The latest 1.3.x release is 1.3.1, released 25 July 2010. -

    - -

    Upcoming Releases

    - -

    -Our goal is to meet the dates -for release candidates and the releases themselves; -the beta checkpoint dates are our best guess as this was published, -but they may be adjusted without notice. -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Estimated dateTypeComments
    May 2010CkptBeta for 2.0; breaks backward compatibility
    June 2010RCRelease candidate for 2.0.
    June 20102.0Public release that breaks backward compatibility and drops deprecated features
    July 2010CkptBeta for testing new features.
    Aug 2010CkptBeta for testing new features.
    Sept 2010RCRelease candidate for 2.1.
    Oct 20102.1First minor release of v2
    - - - -

    Release Planning

    - -

    Release numbering

    - -

    -Our release numbers are of the form major.minor.revision. -

    - -
      -
    • -Major release (1.0, 2.0, 3.0, etc.) -

      -The major number increments when one of two things happens: -

      -
        -
      • The release knowingly breaks backwards compatibility in some way. -
      • The release knowingly causes a rebuild when you upgrade. -
      -

      -Our goal is that as a user of SCons, -you should always be able to upgrade to a later -release of the same major version -with complete confidence that your build will not break. -We expect that our major releases will be long-lived platforms -with many minor releases to add functionality and fix bugs. -

      -
    • -
    • -Minor release (1.1, 1.2, 1.3, etc.) -

      -Minor numbers increment for releases -that add new functionality and/or bug fixes -to an existing major release. -Any new functionality will never knowingly break backwards compatibility -with any previous minor releases from the same major release. -

      -
    • -
    • -Bug-fix revisions (1.0.1, 1.1.1, 1.2.1, etc.) -

      -Revision numbers are appended and/or incremented -whenever a critical bug fix is necessary -for a major or minor release. -Because most new functionality and bug fixes -will be delivered in minor releases, -we expect that there will be few of these—at most -one per minor release. -

      -
    • -
    • -Release candidates (x.y.z.dyyyymmdd) -

      -A release candidates is a special form of checkpoint -(see below) -that is expected to be the next major or minor release. -If blocking issues show up in the candidate, -another candidate will normally be issued -(potentially delaying the release date), -otherwise the candidate will be repackaged as the major or minor release. -

      -
    • -
    • -Checkpoints (x.y.z.dyyyymmdd) -

      -A checkpoint has a 'dyyymmdd' suffix -and is made every couple of weeks between major or minor releases. -It is intended for beta testing new features -and for ensuring that bug fixes work as intended. -Although existing features from the previous release will not change, -compatibility of features under test is not guaranteed between checkpoints -(i.e., the implementation of the feature may change). -Checkpoints are intended not only to allow for wider testing, -but also to make new features available to users -(who may urgently need one of them) -in advance of them being published in the next major or minor release. -

      -
    • -
    - -
    - - - diff --git a/www/schedule b/www/schedule deleted file mode 100644 index 4b1e9eb..0000000 --- a/www/schedule +++ /dev/null @@ -1,19 +0,0 @@ -#=2008-09-13 1.0.1 -#=2008-10-01 Release candidate for 1.1. -#=2008-10-10 1.1.0 -=2008-12-01 1.1.0 -+4 rc Release candidate for 1.2. (released 7-Dec) -+7 1.2 Second minor release of 1.0. (released 21-Dec) -+17 ck Beta for testing new features. -+14 ck Beta for testing new features. -+7 rc Release candidate for 1.3. -+7 1.3 Third minor release of 1.0. -+7 ck Beta for 2.0; breaks backward compatibility -#+7 ck Beta for 2.0. -+7 rc Release candidate for 2.0. -+7 rc Release candidate for 2.0, if needed. -+7 2.0 Public release that breaks backward compatibility and drops deprecated features -+21 ck Beta for testing new features. -+21 ck Beta for testing new features. -+21 rc Release candidate for 2.1. -+7 2.1 First minor release of 2.0 -- cgit v0.12 From f7340cd04f81e686907dcbb301cc4e385684be35 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Mon, 30 Apr 2018 10:27:28 -0400 Subject: [ci skip] Removing obsolete administrative script scons-cdist. Leftover from when SCons used aegis for revision control --- bin/scons-cdist | 272 -------------------------------------------------------- 1 file changed, 272 deletions(-) delete mode 100644 bin/scons-cdist diff --git a/bin/scons-cdist b/bin/scons-cdist deleted file mode 100644 index 58b1bae..0000000 --- a/bin/scons-cdist +++ /dev/null @@ -1,272 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 The SCons Foundation -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY -# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -PROG=`basename $0` -NOARGFLAGS="afhlnqrstz" -ARGFLAGS="p:" -ALLFLAGS="${NOARGFLAGS}${ARGFLAGS}" -USAGE="Usage: ${PROG} [-${NOARGFLAGS}] [-p project] change" - -HELP="$USAGE - - -a Update the latest Aegis baseline (aedist) file. - -f Force update, skipping up-front sanity check. - -h Print this help message and exit. - -l Update the local CVS repository. - -n Don't execute, just echo commands. - -p project Set the Aegis project. - -q Quiet, don't print commands before executing them. - -r Rsync the Aegis repository to SourceForge. - -s Update the sourceforge.net CVS repository. - -t Update the tigris.org CVS repository. - -z Update the latest .tar.gz and .zip files. -" - -DO="" -PRINT="echo" -EXECUTE="eval" -SANITY_CHECK="yes" - -while getopts $ALLFLAGS FLAG; do - case $FLAG in - a | l | r | s | t | z ) - DO="${DO}${FLAG}" - ;; - f ) - SANITY_CHECK="no" - ;; - h ) - echo "${HELP}" - exit 0 - ;; - n ) - EXECUTE=":" - ;; - p ) - AEGIS_PROJECT="${OPTARG}" - ;; - q ) - PRINT=":" - ;; - * ) - echo "FLAG = ${FLAG}" >&2 - echo "${USAGE}" >&2 - exit 1 - ;; - esac -done - -shift `expr ${OPTIND} - 1` - -if test "X$1" = "X"; then - echo "${USAGE}" >&2 - exit 1 -fi - -if test "X${AEGIS_PROJECT}" = "X"; then - echo "$PROG: No AEGIS_PROJECT set." >&2 - echo "${USAGE}" >&2 - exit 1 -fi - -if test "X$DO" = "X"; then - DO="alrstz" -fi - -cmd() -{ - $PRINT "$*" - $EXECUTE "$*" -} - -CHANGE=$1 - -if test "X${SANITY_CHECK}" = "Xyes"; then - SCM="cvs" - SCMROOT="/home/scons/CVSROOT/scons" - DELTA=`aegis -l -ter cd ${CHANGE} | sed -n 's/.*, Delta \([0-9]*\)\./\1/p'` - if test "x${DELTA}" = "x"; then - echo "${PROG}: Could not find delta for change ${CHANGE}." >&2 - echo "Has this finished integrating? Change ${CHANGE} not distributed." >&2 - exit 1 - fi - PREV_DELTA=`expr ${DELTA} - 1` - COMMAND="scons-scmcheck -D ${PREV_DELTA} -d q -p ${AEGIS_PROJECT} -s ${SCM} ${SCMROOT}" - $PRINT "${COMMAND}" - OUTPUT=`${COMMAND}` - if test "X${OUTPUT}" != "X"; then - echo "${PROG}: ${SCMROOT} is not up to date:" >&2 - echo "${OUTPUT}" >& 2 - echo "Did you skip any changes? Change ${CHANGE} not distributed." >&2 - exit 1 - fi -fi - -if test X$EXECUTE != "X:" -a "X$SSH_AGENT_PID" = "X"; then - eval `ssh-agent` - ssh-add - trap 'eval `ssh-agent -k`; exit' 0 1 2 3 15 -fi - -cd - -BASELINE=`aesub -p ${AEGIS_PROJECT} -c ${CHANGE} '${Project trunk_name}'` - -TMPBLAE="/tmp/${BASELINE}.ae" -TMPCAE="/tmp/${AEGIS_PROJECT}.C${CHANGE}.ae" - -# Original values for SourceForge. -#SFLOGIN="stevenknight" -#SFHOST="scons.sourceforge.net" -#SFDEST="/home/groups/s/sc/scons/htdocs" - -SCONSLOGIN="scons" -SCONSHOST="manam.pair.com" -#SCONSDEST="public_html/production" -SCONSDEST="public_ftp" - -# -# Copy the baseline .ae to the constant location on SourceForge. -# -case "${DO}" in -*a* ) - cmd "aedist -s -bl -p ${AEGIS_PROJECT} > ${TMPBLAE}" - cmd "scp ${TMPBLAE} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/${BASELINE}.ae" - cmd "rm ${TMPBLAE}" - ;; -esac - -# -# Copy the latest .tar.gz and .zip files to the constant location on -# SourceForge. -# -case "${DO}" in -*z* ) - BUILD_DIST=`aegis -p ${AEGIS_PROJECT} -cd -bl`/build/dist - SCONS_SRC_TAR_GZ=`echo ${AEGIS_PROJECT} | sed 's/scons./scons-src-/'`*.tar.gz - SCONS_SRC_ZIP=`echo ${AEGIS_PROJECT} | sed 's/scons./scons-src-/'`*.zip - cmd "scp ${BUILD_DIST}/${SCONS_SRC_TAR_GZ} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/scons-src-latest.tar.gz" - cmd "scp ${BUILD_DIST}/${SCONS_SRC_ZIP} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/scons-src-latest.zip" -esac - -# -# Sync Aegis tree with SourceForge. -# -# Cribbed and modified from Peter Miller's same-named script in -# /home/groups/a/ae/aegis/aegis at SourceForge. -# -# Guide to what this does with rsync: -# -# --rsh=ssh use ssh for the transfer -# -l copy symlinks as symlinks -# -p preserve permissions -# -r recursive -# -t preserve times -# -z compress data -# --stats file transfer statistics -# --exclude exclude files matching the pattern -# --delete delete files that don't exist locally -# --delete-excluded delete files that match the --exclude patterns -# --progress show progress during the transfer -# -v verbose -# -# We no longer use the --stats option. -# -case "${DO}" in -*r* ) - LOCAL=/home/scons/scons - REMOTE=/home/groups/s/sc/scons/scons - cmd "/usr/bin/rsync --rsh='ssh -l stevenknight' \ - -l -p -r -t -z \ - --exclude build \ - --exclude '*,D' \ - --exclude '*.pyc' \ - --exclude aegis.log \ - --exclude '.sconsign*' \ - --delete --delete-excluded \ - --progress -v \ - ${LOCAL}/. scons.sourceforge.net:${REMOTE}/." - ;; -esac - -# -# Sync the CVS tree with the local repository. -# -case "${DO}" in -*l* ) - ( - export CVSROOT=/home/scons/CVSROOT/scons - #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/baldmt.com/scons" - cmd "ae-cvs-ci ${AEGIS_PROJECT} ${CHANGE}" - ) - ;; -esac - -# -# Sync the Subversion tree with Tigris.org. -# -case "${DO}" in -*t* ) - ( - SVN=http://scons.tigris.org/svn/scons - case ${AEGIS_PROJECT} in - scons.0.96 ) - SVN_URL=${SVN}/branches/core - ;; - scons.0.96.513 ) - SVN_URL=${SVN}/branches/sigrefactor - ;; - * ) - echo "$PROG: Don't know SVN branch for '${AEGIS_PROJECT}'" >&2 - exit 1 - ;; - esac - SVN_CO_FLAGS="--username stevenknight" - #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/tigris.org/scons" - cmd "ae-svn-ci ${AEGIS_PROJECT} ${CHANGE} ${SVN_URL} ${SVN_CO_FLAGS}" - ) - ;; -esac - -# -# Sync the CVS tree with SourceForge. -# -case "${DO}" in -*s* ) - ( - export CVS_RSH=ssh - export CVSROOT=:ext:stevenknight@scons.cvs.sourceforge.net:/cvsroot/scons - #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/sourceforge.net/scons" - cmd "ae-cvs-ci ${AEGIS_PROJECT} ${CHANGE}" - ) - ;; -esac - -# -# Send the change .ae to the scons-aedist mailing list -# -# The subject requires editing by hand... -# -#aedist -s -p ${AEGIS_PROJECT} ${CHANGE} > ${TMPCAE} -#aegis -l -p ${AEGIS_PROJECT} -c ${CHANGE} cd | -# pine -attach_and_delete ${TMPCAE} scons-aedist@lists.sourceforge.net -- cgit v0.12 From 3ce0e0e670245e16bf477cb31d48b770844cf1bb Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Wed, 2 May 2018 08:24:30 -0600 Subject: Module used an undefined class name ArchDefinition is defined, then ArchitectureDefinition is used. It is not clear this is actually used (the problem was flagged by Sphinx, which was walking through every file), but let's not leave it broken. Signed-off-by: Mats Wichmann --- src/engine/SCons/Tool/MSCommon/arch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/SCons/Tool/MSCommon/arch.py b/src/engine/SCons/Tool/MSCommon/arch.py index 9bcc221..ad17959 100644 --- a/src/engine/SCons/Tool/MSCommon/arch.py +++ b/src/engine/SCons/Tool/MSCommon/arch.py @@ -37,22 +37,22 @@ class ArchDefinition(object): self.synonyms = synonyms SupportedArchitectureList = [ - ArchitectureDefinition( + ArchDefinition( 'x86', ['i386', 'i486', 'i586', 'i686'], ), - ArchitectureDefinition( + ArchDefinition( 'x86_64', ['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'], ), - ArchitectureDefinition( + ArchDefinition( 'ia64', ['IA64'], ), - ArchitectureDefinition( + ArchDefinition( 'arm', ['ARM'], ), -- cgit v0.12 From a7ba3605768e5cd2f797d11913a23726f55f2eba Mon Sep 17 00:00:00 2001 From: William Deegan Date: Tue, 8 May 2018 21:50:14 -0400 Subject: Fix issue where at least test/Java/JAR.py test fail only on windows because source file test1.class is first converted to a File object, and then has toupper() called on it (via _my_normcase only on windows), and the result of which is the source list is empty which causes the fake jar part of the test to fail --- src/engine/SCons/Tool/jar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/SCons/Tool/jar.py b/src/engine/SCons/Tool/jar.py index 5e3711a..6e319c1 100644 --- a/src/engine/SCons/Tool/jar.py +++ b/src/engine/SCons/Tool/jar.py @@ -140,7 +140,7 @@ def Jar(env, target = None, source = [], *args, **kw): # source for jar, otherwise turn it into a class file then # return the source def file_to_class(s): - if(str(_my_normcase(s)).endswith(java_suffix)): + if _my_normcase(str(s)).endswith(java_suffix): return env.JavaClassFile(source = s, *args, **kw) else: return [env.fs.File(s)] -- cgit v0.12 From 96d638297a71925053b164578506f44deac12acf Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 11 May 2018 17:26:39 -0400 Subject: change test to skip initializing any tools in DefaulEnvironment. Should speed up test on windows a bit --- test/Java/JAR.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/Java/JAR.py b/test/Java/JAR.py index d62696c..faf01a3 100644 --- a/test/Java/JAR.py +++ b/test/Java/JAR.py @@ -52,6 +52,7 @@ sys.exit(0) """) test.write('SConstruct', """ +DefaultEnvironment(tools=[]) env = Environment(tools = ['jar'], JAR = r'%(_python_)s myjar.py') env.Jar(target = 'test1.jar', source = 'test1.class') @@ -70,6 +71,7 @@ test.must_match('test1.jar', "test1.class\nline 3\n", mode='r') if os.path.normcase('.class') == os.path.normcase('.CLASS'): test.write('SConstruct', """ +DefaultEnvironment(tools=[]) env = Environment(tools = ['jar'], JAR = r'%(_python_)s myjar.py') env.Jar(target = 'test2.jar', source = 'test2.CLASS') @@ -95,6 +97,7 @@ sys.exit(0) """) test.write('SConstruct', """ +DefaultEnvironment(tools=[]) env = Environment(tools = ['jar'], JAR = r'%(_python_)s myjar2.py', JARFLAGS='cvf') @@ -126,6 +129,7 @@ where_jar = test.java_where_jar() test.file_fixture('wrapper_with_args.py') test.write('SConstruct', """ +DefaultEnvironment(tools=[]) foo = Environment(tools = ['javac', 'jar'], JAVAC = r'%(where_javac)s', JAR = r'%(where_jar)s') @@ -249,6 +253,8 @@ test.subdir('testdir2', # simple SConstruct which passes the 3 .java as source # and extracts the jars back to classes test.write(['testdir2', 'SConstruct'], """ +DefaultEnvironment(tools=[]) + foo = Environment() foo.Jar(target = 'foobar', source = [ 'com/javasource/JavaFile1.java', @@ -345,6 +351,8 @@ test.subdir('listOfLists', # test varient dir and lists of lists test.write(['listOfLists', 'SConstruct'], """ +DefaultEnvironment(tools=[]) + foo = Environment() foo.VariantDir('build', 'src', duplicate=0) foo.VariantDir('test', '../manifest_dir', duplicate=0) @@ -435,6 +443,8 @@ test.subdir('testdir3', # Create the jars then extract them back to check contents test.write(['testdir3', 'SConstruct'], """ +DefaultEnvironment(tools=[]) + foo = Environment() bar = foo.Clone() foo.Java(target = 'classes', source = 'com/sub/foo') -- cgit v0.12 From f48da72a3156d90bd6d58a840df1be2b013c5e9b Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 11 May 2018 17:28:57 -0400 Subject: Add some reasonable default windows paths to find java. --- testing/framework/TestSCons.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index ba4e06b..d847845 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -692,20 +692,29 @@ class TestSCons(TestCommon): env = SCons.Environment.Environment() self._java_env[version] = env - if version: - patterns = [ - '/usr/java/jdk%s*/bin' % version, - '/usr/lib/jvm/*-%s*/bin' % version, - '/usr/local/j2sdk%s*/bin' % version, - ] + if sys.platform == 'win32': + patterns = [ + 'C:/Program Files/Java/jdk%s*/bin'%version, + ] + else: + patterns = [ + '/usr/java/jdk%s*/bin' % version, + '/usr/lib/jvm/*-%s*/bin' % version, + '/usr/local/j2sdk%s*/bin' % version, + ] java_path = self.paths(patterns) + [env['ENV']['PATH']] else: - patterns = [ - '/usr/java/latest/bin', - '/usr/lib/jvm/*/bin', - '/usr/local/j2sdk*/bin', - ] + if sys.platform == 'win32': + patterns = [ + 'C:/Program Files/Java/jdk*/bin', + ] + else: + patterns = [ + '/usr/java/latest/bin', + '/usr/lib/jvm/*/bin', + '/usr/local/j2sdk*/bin', + ] java_path = self.paths(patterns) + [env['ENV']['PATH']] env['ENV']['PATH'] = os.pathsep.join(java_path) -- cgit v0.12 From 5efd04aaed4c9700a606e11733656225c5d933e8 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 11 May 2018 17:44:15 -0400 Subject: Add wildcards so we'll catch both 32 and 64 bit versions of java on windows. --- testing/framework/TestSCons.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index d847845..c32e06a 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -695,7 +695,7 @@ class TestSCons(TestCommon): if version: if sys.platform == 'win32': patterns = [ - 'C:/Program Files/Java/jdk%s*/bin'%version, + 'C:/Program Files*/Java/jdk%s*/bin'%version, ] else: patterns = [ @@ -707,7 +707,7 @@ class TestSCons(TestCommon): else: if sys.platform == 'win32': patterns = [ - 'C:/Program Files/Java/jdk*/bin', + 'C:/Program Files*/Java/jdk*/bin', ] else: patterns = [ -- cgit v0.12 From 14c4bbd1d9e5f87a7964fd69a60e7d21d69319ea Mon Sep 17 00:00:00 2001 From: maiphi <39464783+maiphi@users.noreply.github.com> Date: Sun, 20 May 2018 19:42:52 +0200 Subject: Make class SCons.Subst.Literal hashable When using Python 3, substitution of Literal objects requires the objects to be hashable, otherwise an error will be thrown. The hash value is that of the lstr member. --- src/engine/SCons/Subst.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engine/SCons/Subst.py b/src/engine/SCons/Subst.py index 68d247f..0b4190b 100644 --- a/src/engine/SCons/Subst.py +++ b/src/engine/SCons/Subst.py @@ -86,6 +86,9 @@ class Literal(object): def __neq__(self, other): return not self.__eq__(other) + def __hash__(self): + return hash(self.lstr) + class SpecialAttrWrapper(object): """This is a wrapper for what we call a 'Node special attribute.' This is any of the attributes of a Node that we can reference from -- cgit v0.12 From 8d504fd00127bdcffcf436993aa8e8c89011c63e Mon Sep 17 00:00:00 2001 From: maiphi <39464783+maiphi@users.noreply.github.com> Date: Sun, 20 May 2018 20:13:45 +0200 Subject: Update Changes.txt with hashable Literal --- src/CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index a4590d0..f9cb5c4 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -7,6 +7,10 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE + From Philipp Maierhöfer + - Added a __hash__ method to the class Scons.Subst.Literal. Required when substituting Literal + objects when SCons runs with Python 3. + From Richard West: - Add SConstruct.py, Sconstruct.py, sconstruct.py to the search path for the root SConstruct file. Allows easier debugging within Visual Studio -- cgit v0.12 From 21335a0ef0463d51ecc9ea9cc86c3388415f12aa Mon Sep 17 00:00:00 2001 From: Philipp Maierhoefer Date: Mon, 21 May 2018 11:43:56 +0200 Subject: Added a test case for expansion of Literal objects in ${_concat()} --- test/Subst/Literal.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/Subst/Literal.py diff --git a/test/Subst/Literal.py b/test/Subst/Literal.py new file mode 100644 index 0000000..38ed3c4 --- /dev/null +++ b/test/Subst/Literal.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that Literal objects expand correctly in ${_concat()}. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """\ +env = Environment(PRE='pre=', MID=Literal('\$$ORIGIN'), SUF='') +print(env.subst('${_concat(PRE, MID, SUF, __env__)}')) +""") + +test.run() + +expect = """\ +pre=\$ORIGIN +""" + +test.run(arguments='-Q -q', stdout=expect) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: -- cgit v0.12 From 75059479aff3cd50b9e547858a56c39eabeb111f Mon Sep 17 00:00:00 2001 From: maiphi <39464783+maiphi@users.noreply.github.com> Date: Tue, 22 May 2018 13:53:23 +0200 Subject: Added missing pass_test() to Literal.py test --- test/Subst/Literal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Subst/Literal.py b/test/Subst/Literal.py index 38ed3c4..dec243d 100644 --- a/test/Subst/Literal.py +++ b/test/Subst/Literal.py @@ -45,6 +45,8 @@ pre=\$ORIGIN test.run(arguments='-Q -q', stdout=expect) +test.pass_test() + # Local Variables: # tab-width:4 # indent-tabs-mode:nil -- cgit v0.12 From 880fce9c7982397fe16f49160591199d1e117c80 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Thu, 24 May 2018 21:55:52 -0500 Subject: issue-3130: udpated travis file to update apt-get. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9d8b96d..a1f58b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,10 @@ dist: trusty language: python +addons: + apt: + update: true + install: - ./.travis/install.sh -- cgit v0.12 From c299f365221f80e2325d391e9c15f4c1e2632486 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Wed, 27 Dec 2017 17:46:38 -0500 Subject: fixed swig tests to work on windows --- test/SWIG/SWIGPATH.py | 5 +++-- test/SWIG/implicit-dependencies.py | 5 +++-- test/SWIG/live.py | 11 ++++++++--- test/SWIG/recursive-includes-cpp.py | 6 +++++- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/test/SWIG/SWIGPATH.py b/test/SWIG/SWIGPATH.py index 55e8d7e..d516c0c 100644 --- a/test/SWIG/SWIGPATH.py +++ b/test/SWIG/SWIGPATH.py @@ -40,7 +40,8 @@ python = test.where_is('python') if not python: test,skip_test('Can not find installed "python", skipping test.\n') - +swig = swig.replace('\\','/') +python = python.replace('\\','/') test.subdir('inc1', 'inc2') test.write(['inc2', 'dependency.i'], """\ @@ -95,4 +96,4 @@ test.pass_test() # tab-width:4 # indent-tabs-mode:nil # End: -# vim: set expandtab tabstop=4 shiftwidth=4: +# vim: set expandtab tabstop=4 shiftwidth=4: \ No newline at end of file diff --git a/test/SWIG/implicit-dependencies.py b/test/SWIG/implicit-dependencies.py index 465a0d6..8664bf6 100644 --- a/test/SWIG/implicit-dependencies.py +++ b/test/SWIG/implicit-dependencies.py @@ -40,7 +40,8 @@ python = test.where_is('python') if not python: test.skip_test('Can not find installed "python", skipping test.\n') - +swig = swig.replace('\\','/') +python = python.replace('\\','/') test.write("dependency.i", """\ %module dependency """) @@ -75,4 +76,4 @@ test.pass_test() # tab-width:4 # indent-tabs-mode:nil # End: -# vim: set expandtab tabstop=4 shiftwidth=4: +# vim: set expandtab tabstop=4 shiftwidth=4: \ No newline at end of file diff --git a/test/SWIG/live.py b/test/SWIG/live.py index 05971aa..684cff1 100644 --- a/test/SWIG/live.py +++ b/test/SWIG/live.py @@ -48,9 +48,14 @@ if not swig: python, python_include, python_libpath, python_lib = \ test.get_platform_python_info() -Python_h = os.path.join(python_include, 'Python.h') +Python_h = python_include + '/Python.h' if not os.path.exists(Python_h): test.skip_test('Can not find %s, skipping test.\n' % Python_h) +swig = swig.replace('\\','/') +python = python.replace('\\','/') +python_include = python_include.replace('\\','/') +python_libpath = python_libpath.replace('\\','/') +python_lib = python_lib.replace('\\','/') # handle testing on other platforms: ldmodule_prefix = '_' @@ -142,7 +147,7 @@ test.up_to_date(arguments = ldmodule_prefix+'foo' + _dll) test.run(arguments = ldmodule_prefix+'bar' + _dll) -test.must_match('wrapper.out', "wrapper.py\n") +test.must_match('wrapper.out', "wrapper.py" + os.linesep) test.run(program = python, stdin = """\ from __future__ import print_function @@ -164,4 +169,4 @@ test.pass_test() # tab-width:4 # indent-tabs-mode:nil # End: -# vim: set expandtab tabstop=4 shiftwidth=4: +# vim: set expandtab tabstop=4 shiftwidth=4: \ No newline at end of file diff --git a/test/SWIG/recursive-includes-cpp.py b/test/SWIG/recursive-includes-cpp.py index dbcac6d..0baa195 100644 --- a/test/SWIG/recursive-includes-cpp.py +++ b/test/SWIG/recursive-includes-cpp.py @@ -30,6 +30,7 @@ in cases of recursive inclusion. """ import os +import sys import TestSCons from SCons.Defaults import DefaultEnvironment @@ -37,6 +38,9 @@ DefaultEnvironment( tools = [ 'swig' ] ) test = TestSCons.TestSCons() +if sys.platform == 'win32': + test.skip_test('test not setup for windows, skipping test') + # Check for prerequisites of this test. for pre_req in ['swig', 'python']: if not test.where_is(pre_req): @@ -124,4 +128,4 @@ test.pass_test() # tab-width:4 # indent-tabs-mode:nil # End: -# vim: set expandtab tabstop=4 shiftwidth=4: +# vim: set expandtab tabstop=4 shiftwidth=4: \ No newline at end of file -- cgit v0.12 From 14083d5ab0b909eb8626fe8e94b47cfd598f45fc Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 25 May 2018 09:50:56 -0500 Subject: pr-3052: updated swig test to work for windows. --- test/SWIG/recursive-includes-cpp.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/SWIG/recursive-includes-cpp.py b/test/SWIG/recursive-includes-cpp.py index 0baa195..e69b5b9 100644 --- a/test/SWIG/recursive-includes-cpp.py +++ b/test/SWIG/recursive-includes-cpp.py @@ -30,7 +30,6 @@ in cases of recursive inclusion. """ import os -import sys import TestSCons from SCons.Defaults import DefaultEnvironment @@ -38,9 +37,6 @@ DefaultEnvironment( tools = [ 'swig' ] ) test = TestSCons.TestSCons() -if sys.platform == 'win32': - test.skip_test('test not setup for windows, skipping test') - # Check for prerequisites of this test. for pre_req in ['swig', 'python']: if not test.where_is(pre_req): @@ -86,7 +82,7 @@ if sys.platform == 'darwin': env['LIBS']=['python%d.%d'%(sys.version_info[0],sys.version_info[1])] env.SharedLibrary( - 'mod.so', + 'mod', [ "mod.i", "main.c", -- cgit v0.12 From 100f49aa06ff3e8ed09fb77edc787f35d2de10fc Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 25 May 2018 11:26:02 -0700 Subject: Handful of changes to get clang tests passing on windows. Use glob to find other possible default mingw install paths. mingw is needed in many cases on windows to compile/archive. --- src/engine/SCons/Tool/clang.py | 3 ++- src/engine/SCons/Tool/mingw.py | 21 ++++++++++++++++++++- test/Clang/clang_default_environment.py | 5 ++--- test/Clang/clang_shared_library.py | 12 +++++++----- test/Clang/clang_specific_environment.py | 1 + test/Clang/clang_static_library.py | 3 ++- test/Clang/clangxx_default_environment.py | 5 ++--- test/Clang/clangxx_shared_library.py | 11 +++++++---- test/Clang/clangxx_specific_environment.py | 3 ++- test/Clang/clangxx_static_library.py | 3 ++- 10 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/engine/SCons/Tool/clang.py b/src/engine/SCons/Tool/clang.py index c51e753..cbb48cb 100644 --- a/src/engine/SCons/Tool/clang.py +++ b/src/engine/SCons/Tool/clang.py @@ -56,7 +56,8 @@ def generate(env): if env['PLATFORM'] == 'win32': # Ensure that we have a proper path for clang - clang = SCons.Tool.find_program_path(env, compilers[0], default_paths=get_clang_install_dirs(env['PLATFORM'])) + clang = SCons.Tool.find_program_path(env, compilers[0], + default_paths=get_clang_install_dirs(env['PLATFORM'])) if clang: clang_bin_dir = os.path.dirname(clang) env.AppendENVPath('PATH', clang_bin_dir) diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py index f2e9442..738460d 100644 --- a/src/engine/SCons/Tool/mingw.py +++ b/src/engine/SCons/Tool/mingw.py @@ -35,6 +35,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os import os.path +import glob import SCons.Action import SCons.Builder @@ -115,10 +116,28 @@ res_builder = SCons.Builder.Builder(action=res_action, suffix='.o', SCons.Tool.SourceFileScanner.add_scanner('.rc', SCons.Defaults.CScan) # This is what we search for to find mingw: -key_program = 'mingw32-gcc' +# key_program = 'mingw32-gcc' +key_program = 'mingw32-make' + + + +def find_version_specific_mingw_paths(): + """ + One example of default mingw install paths is: + C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev2\mingw64\bin + + Use glob'ing to find such and add to mingw_paths + """ + new_paths = glob.glob(r"C:\mingw-w64\*\mingw64\bin") + + return new_paths def generate(env): + global mingw_paths + # Check for reasoanble mingw default paths + mingw_paths +=find_version_specific_mingw_paths() + mingw = SCons.Tool.find_program_path(env, key_program, default_paths=mingw_paths) if mingw: mingw_bin_dir = os.path.dirname(mingw) diff --git a/test/Clang/clang_default_environment.py b/test/Clang/clang_default_environment.py index 4ac1c68..5ebd839 100644 --- a/test/Clang/clang_default_environment.py +++ b/test/Clang/clang_default_environment.py @@ -35,9 +35,8 @@ if not test.where_is('clang'): ## This will likely NOT use clang test.write('SConstruct', """ -env = Environment() -if env['CC'] != 'clang': - env['CC'] = 'clang' +DefaultEnvironment(tools=[]) +env = Environment(tools=['clang','link']) env.Program('foo.c') """) diff --git a/test/Clang/clang_shared_library.py b/test/Clang/clang_shared_library.py index 5e4d36f..9af3770 100644 --- a/test/Clang/clang_shared_library.py +++ b/test/Clang/clang_shared_library.py @@ -33,20 +33,22 @@ test = TestSCons.TestSCons() if not test.where_is('clang'): test.skip_test("Could not find 'clang', skipping test.\n") -platform = Base()['PLATFORM'] +base = Base() +platform = base['PLATFORM'] if platform == 'posix': - filename = 'foo.os' + filename_options = ['foo.os'] libraryname = 'libfoo.so' elif platform == 'darwin': - filename = 'foo.os' + filename_options = ['foo.os'] libraryname = 'libfoo.dylib' elif platform == 'win32': - filename = 'foo.obj' + filename_options = ['foo.obj','foo.os'] libraryname = 'foo.dll' else: test.fail_test() test.write('SConstruct', """\ +DefaultEnvironment(tools=[]) env = Environment(tools=['clang', 'link']) env.SharedLibrary('foo', 'foo.c') """) @@ -59,7 +61,7 @@ int bar() { test.run() -test.must_exist(test.workpath(filename)) +test.must_exist_one_of([test.workpath(f) for f in filename_options]) test.must_exist(test.workpath(libraryname)) test.pass_test() diff --git a/test/Clang/clang_specific_environment.py b/test/Clang/clang_specific_environment.py index 7266a9f..81991f0 100644 --- a/test/Clang/clang_specific_environment.py +++ b/test/Clang/clang_specific_environment.py @@ -33,6 +33,7 @@ if not test.where_is('clang'): test.skip_test("Could not find 'clang', skipping test.\n") test.write('SConstruct', """\ +DefaultEnvironment(tools=[]) env = Environment(tools=['clang', 'link']) env.Program('foo.c') """) diff --git a/test/Clang/clang_static_library.py b/test/Clang/clang_static_library.py index 39e9931..7dedc6f 100644 --- a/test/Clang/clang_static_library.py +++ b/test/Clang/clang_static_library.py @@ -33,7 +33,8 @@ if not test.where_is('clang'): test.skip_test("Could not find 'clang', skipping test.\n") test.write('SConstruct', """\ -env = Environment(tools=['clang', 'ar']) +DefaultEnvironment(tools=[]) +env = Environment(tools=['mingw','clang', 'ar']) env.StaticLibrary('foo', 'foo.c') """) diff --git a/test/Clang/clangxx_default_environment.py b/test/Clang/clangxx_default_environment.py index beef1e5..5e46404 100644 --- a/test/Clang/clangxx_default_environment.py +++ b/test/Clang/clangxx_default_environment.py @@ -35,9 +35,8 @@ if not test.where_is('clang'): ## This will likely NOT use clang++. test.write('SConstruct', """\ -env = Environment() -if env['CXX'] != 'clang++': - env['CXX'] = 'clang++' +DefaultEnvironment(tools=[]) +env = Environment(tools=['clangxx','link']) env.Program('foo.cpp') """) diff --git a/test/Clang/clangxx_shared_library.py b/test/Clang/clangxx_shared_library.py index d6337ba..6240299 100644 --- a/test/Clang/clangxx_shared_library.py +++ b/test/Clang/clangxx_shared_library.py @@ -36,18 +36,21 @@ if not test.where_is('clang'): platform = Base()['PLATFORM'] if platform == 'posix': - filename = 'foo.os' + filename_options = ['foo.os'] libraryname = 'libfoo.so' elif platform == 'darwin': - filename = 'foo.os' + filename_options = ['foo.os'] libraryname = 'libfoo.dylib' elif platform == 'win32': - filename = 'foo.obj' + filename_options = ['foo.obj','foo.os'] libraryname = 'foo.dll' else: test.fail_test() + + test.write('SConstruct', """\ +DefaultEnvironment(tools=[]) env = Environment(tools=['clang++', 'link']) env.SharedLibrary('foo', 'foo.cpp') """) @@ -60,7 +63,7 @@ int bar() { test.run() -test.must_exist(test.workpath(filename)) +test.must_exist_one_of([test.workpath(f) for f in filename_options]) test.must_exist(test.workpath(libraryname)) test.pass_test() diff --git a/test/Clang/clangxx_specific_environment.py b/test/Clang/clangxx_specific_environment.py index 773fa94..35540bb 100644 --- a/test/Clang/clangxx_specific_environment.py +++ b/test/Clang/clangxx_specific_environment.py @@ -33,7 +33,8 @@ if not test.where_is('clang'): test.skip_test("Could not find 'clang++', skipping test.\n") test.write('SConstruct', """\ -env = Environment(tools=['clang++', 'link']) +DefaultEnvironment(tools=[]) +env = Environment(tools=['mingw','clang++', 'link']) env.Program('foo.cpp') """) diff --git a/test/Clang/clangxx_static_library.py b/test/Clang/clangxx_static_library.py index 77ea58e..c768deb 100644 --- a/test/Clang/clangxx_static_library.py +++ b/test/Clang/clangxx_static_library.py @@ -33,7 +33,8 @@ if not test.where_is('clang'): test.skip_test("Could not find 'clang++', skipping test.\n") test.write('SConstruct', """\ -env = Environment(tools=['clang++', 'ar']) +DefaultEnvironment(tools=[]) +env = Environment(tools=['mingw','clang++', 'ar']) env.StaticLibrary('foo', 'foo.cpp') """) -- cgit v0.12 From 49a553beb72d3192f77e3ae39b449daed086c22f Mon Sep 17 00:00:00 2001 From: William Deegan Date: Wed, 17 Jan 2018 23:35:26 -0500 Subject: add another packaging build dir to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4050468..1807b29 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ __pycache__/ build/** bootstrap/** .idea/ +src/build/** # Translations -- cgit v0.12 From acb358617aecd3d7ba974585c386a1e31ae40f71 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Wed, 17 Jan 2018 23:37:06 -0500 Subject: Added find_program_path() to SCons.Tool. Logic was in mingw. Use this to allow tools to search for normal SCOns paths, and then specify alternative default install paths for such tools. This should resolve clang failing on appveyor on windows --- src/engine/SCons/Tool/__init__.py | 29 +++++++++++++++++++++++++++++ src/engine/SCons/Tool/clang.py | 7 +++++++ src/engine/SCons/Tool/clangxx.py | 11 ++++++++++- src/engine/SCons/Tool/mingw.py | 31 +++++++------------------------ 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py index 42f84e1..0b340c0 100644 --- a/src/engine/SCons/Tool/__init__.py +++ b/src/engine/SCons/Tool/__init__.py @@ -1247,6 +1247,35 @@ def tool_list(platform, env): return [x for x in tools if x] + +def find_program_path(env, key_program, default_paths=[]): + """ + Find the location of key_program and then return the path it was located at. + Checking the default install locations. + Mainly for windows where tools aren't all installed in /usr/bin,etc + :param env: Current Environment() + :param key_program: Program we're using to locate the directory to add to PATH. + """ + # First search in the SCons path + path=env.WhereIs(key_program) + if (path): + return path + # then the OS path: + path=SCons.Util.WhereIs(key_program) + if (path): + return path + + # If that doesn't work try default location for mingw + save_path = env['ENV']['PATH'] + for p in default_paths: + env.AppendENVPath('PATH',p) + path = env.WhereIs(key_program) + if not path: + env['ENV']['PATH']=save_path + return path + + + # Local Variables: # tab-width:4 # indent-tabs-mode:nil diff --git a/src/engine/SCons/Tool/clang.py b/src/engine/SCons/Tool/clang.py index 177e6b2..a6bae43 100644 --- a/src/engine/SCons/Tool/clang.py +++ b/src/engine/SCons/Tool/clang.py @@ -52,6 +52,13 @@ def generate(env): """Add Builders and construction variables for clang to an Environment.""" SCons.Tool.cc.generate(env) + if env['PLATFORM'] == 'win32': + # Ensure that we have a proper path for clang + clang = SCons.Tool.find_program_path(env, compilers[0], default_paths=[r'C:\Program Files\LLVM\bin',]) + if clang: + clang_bin_dir = os.path.dirname(clang) + env.AppendENVPath('PATH', clang_bin_dir) + env['CC'] = env.Detect(compilers) or 'clang' if env['PLATFORM'] in ['cygwin', 'win32']: env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') diff --git a/src/engine/SCons/Tool/clangxx.py b/src/engine/SCons/Tool/clangxx.py index dd501af..63f1b0d 100644 --- a/src/engine/SCons/Tool/clangxx.py +++ b/src/engine/SCons/Tool/clangxx.py @@ -66,13 +66,22 @@ def generate(env): env['SHOBJSUFFIX'] = '.pic.o' elif env['PLATFORM'] == 'sunos': env['SHOBJSUFFIX'] = '.pic.o' + elif env['PLATFORM'] == 'win32': + # Ensure that we have a proper path for clang++ + clangxx = SCons.Tool.find_program_path(env, compilers[0], default_paths=[r'C:\Program Files\LLVM\bin',]) + if clangxx: + clangxx_bin_dir = os.path.dirname(clangxx) + env.AppendENVPath('PATH', clangxx_bin_dir) + # determine compiler version if env['CXX']: pipe = SCons.Action._subproc(env, [env['CXX'], '--version'], stdin='devnull', stderr='devnull', stdout=subprocess.PIPE) - if pipe.wait() != 0: return + if pipe.wait() != 0: + return + # clang -dumpversion is of no use line = pipe.stdout.readline() if sys.version_info[0] > 2: diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py index 778db3c..4121902 100644 --- a/src/engine/SCons/Tool/mingw.py +++ b/src/engine/SCons/Tool/mingw.py @@ -42,26 +42,6 @@ import SCons.Defaults import SCons.Tool import SCons.Util -# This is what we search for to find mingw: -key_program = 'mingw32-gcc' - -def find(env): - # First search in the SCons path - path=env.WhereIs(key_program) - if (path): - return path - # then the OS path: - path=SCons.Util.WhereIs(key_program) - if (path): - return path - - # If that doesn't work try default location for mingw - save_path=env['ENV']['PATH'] - env.AppendENVPath('PATH',r'c:\MinGW\bin') - path =env.WhereIs(key_program) - if not path: - env['ENV']['PATH']=save_path - return path def shlib_generator(target, source, env, for_signature): cmd = SCons.Util.CLVar(['$SHLINK', '$SHLINKFLAGS']) @@ -126,12 +106,15 @@ res_builder = SCons.Builder.Builder(action=res_action, suffix='.o', source_scanner=SCons.Tool.SourceFileScanner) SCons.Tool.SourceFileScanner.add_scanner('.rc', SCons.Defaults.CScan) +# This is what we search for to find mingw: +key_program = 'mingw32-gcc' + + def generate(env): - mingw = find(env) + mingw = SCons.Tool.find_program_path(env, key_program, default_paths=[r'c:\MinGW\bin',]) if mingw: - dir = os.path.dirname(mingw) - env.PrependENVPath('PATH', dir ) - + mingw_bin_dir = os.path.dirname(mingw) + env.AppendENVPath('PATH', mingw_bin_dir) # Most of mingw is the same as gcc and friends... gnu_tools = ['gcc', 'g++', 'gnulink', 'ar', 'gas', 'gfortran', 'm4'] -- cgit v0.12 From 00ade7bc85441688369efef881d915804191e0eb Mon Sep 17 00:00:00 2001 From: William Deegan Date: Wed, 14 Feb 2018 11:44:59 -0500 Subject: Update default paths for clang compilers. This should hopefully fix some appveyor failures. --- src/engine/SCons/Tool/clang.py | 5 ++++- src/engine/SCons/Tool/clang/__init__.py | 16 ++++++++++++++++ src/engine/SCons/Tool/clangxx.py | 4 +++- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/engine/SCons/Tool/clang/__init__.py diff --git a/src/engine/SCons/Tool/clang.py b/src/engine/SCons/Tool/clang.py index a6bae43..aba5598 100644 --- a/src/engine/SCons/Tool/clang.py +++ b/src/engine/SCons/Tool/clang.py @@ -45,6 +45,8 @@ import sys import SCons.Util import SCons.Tool.cc +from SCons.Tool.clang import get_clang_install_dirs + compilers = ['clang'] @@ -54,7 +56,7 @@ def generate(env): if env['PLATFORM'] == 'win32': # Ensure that we have a proper path for clang - clang = SCons.Tool.find_program_path(env, compilers[0], default_paths=[r'C:\Program Files\LLVM\bin',]) + clang = SCons.Tool.find_program_path(env, compilers[0], default_paths=get_clang_install_dirs(env['PLATFORM'])) if clang: clang_bin_dir = os.path.dirname(clang) env.AppendENVPath('PATH', clang_bin_dir) @@ -64,6 +66,7 @@ def generate(env): env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') else: env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC') + # determine compiler version if env['CC']: #pipe = SCons.Action._subproc(env, [env['CC'], '-dumpversion'], diff --git a/src/engine/SCons/Tool/clang/__init__.py b/src/engine/SCons/Tool/clang/__init__.py new file mode 100644 index 0000000..775ba80 --- /dev/null +++ b/src/engine/SCons/Tool/clang/__init__.py @@ -0,0 +1,16 @@ +""" +Common routines and data for clang tools +""" + +clang_win32_dirs = [ + r'C:\Program Files\LLVM\bin', + r'C:\cygwin64\bin', + r'C:\msys64', + r'C:\cygwin\bin', + r'C:\msys', +] + +def get_clang_install_dirs(platform): + if platform == 'win32': + return clang_win32_dirs + else return [] \ No newline at end of file diff --git a/src/engine/SCons/Tool/clangxx.py b/src/engine/SCons/Tool/clangxx.py index 63f1b0d..2bf4cf4 100644 --- a/src/engine/SCons/Tool/clangxx.py +++ b/src/engine/SCons/Tool/clangxx.py @@ -46,6 +46,8 @@ import sys import SCons.Tool import SCons.Util import SCons.Tool.cxx +from SCons.Tool.clang import get_clang_install_dirs + compilers = ['clang++'] @@ -68,7 +70,7 @@ def generate(env): env['SHOBJSUFFIX'] = '.pic.o' elif env['PLATFORM'] == 'win32': # Ensure that we have a proper path for clang++ - clangxx = SCons.Tool.find_program_path(env, compilers[0], default_paths=[r'C:\Program Files\LLVM\bin',]) + clangxx = SCons.Tool.find_program_path(env, compilers[0], default_paths=get_clang_install_dirs(env['PLATFORM'])) if clangxx: clangxx_bin_dir = os.path.dirname(clangxx) env.AppendENVPath('PATH', clangxx_bin_dir) -- cgit v0.12 From d4b41bbd10023f8c391b4bbd2928cc435e19eda3 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 25 Mar 2018 12:53:56 -0400 Subject: rename clang command package to clangCommon as it was breaking imports test. --- src/engine/SCons/Tool/clang.py | 2 +- src/engine/SCons/Tool/clang/__init__.py | 16 ---------------- src/engine/SCons/Tool/clangCommon/__init__.py | 17 +++++++++++++++++ src/engine/SCons/Tool/clangxx.py | 2 +- 4 files changed, 19 insertions(+), 18 deletions(-) delete mode 100644 src/engine/SCons/Tool/clang/__init__.py create mode 100644 src/engine/SCons/Tool/clangCommon/__init__.py diff --git a/src/engine/SCons/Tool/clang.py b/src/engine/SCons/Tool/clang.py index aba5598..c51e753 100644 --- a/src/engine/SCons/Tool/clang.py +++ b/src/engine/SCons/Tool/clang.py @@ -45,7 +45,7 @@ import sys import SCons.Util import SCons.Tool.cc -from SCons.Tool.clang import get_clang_install_dirs +from SCons.Tool.clangCommon import get_clang_install_dirs compilers = ['clang'] diff --git a/src/engine/SCons/Tool/clang/__init__.py b/src/engine/SCons/Tool/clang/__init__.py deleted file mode 100644 index 775ba80..0000000 --- a/src/engine/SCons/Tool/clang/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Common routines and data for clang tools -""" - -clang_win32_dirs = [ - r'C:\Program Files\LLVM\bin', - r'C:\cygwin64\bin', - r'C:\msys64', - r'C:\cygwin\bin', - r'C:\msys', -] - -def get_clang_install_dirs(platform): - if platform == 'win32': - return clang_win32_dirs - else return [] \ No newline at end of file diff --git a/src/engine/SCons/Tool/clangCommon/__init__.py b/src/engine/SCons/Tool/clangCommon/__init__.py new file mode 100644 index 0000000..37efbf6 --- /dev/null +++ b/src/engine/SCons/Tool/clangCommon/__init__.py @@ -0,0 +1,17 @@ +""" +Common routines and data for clang tools +""" + +clang_win32_dirs = [ + r'C:\Program Files\LLVM\bin', + r'C:\cygwin64\bin', + r'C:\msys64', + r'C:\cygwin\bin', + r'C:\msys', +] + +def get_clang_install_dirs(platform): + if platform == 'win32': + return clang_win32_dirs + else: + return [] \ No newline at end of file diff --git a/src/engine/SCons/Tool/clangxx.py b/src/engine/SCons/Tool/clangxx.py index 2bf4cf4..7194d9e 100644 --- a/src/engine/SCons/Tool/clangxx.py +++ b/src/engine/SCons/Tool/clangxx.py @@ -46,7 +46,7 @@ import sys import SCons.Tool import SCons.Util import SCons.Tool.cxx -from SCons.Tool.clang import get_clang_install_dirs +from SCons.Tool.clangCommon import get_clang_install_dirs compilers = ['clang++'] -- cgit v0.12 From 69a0d4b42ca97f034dd6a00fd72c41a6719e225c Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 25 Mar 2018 13:41:06 -0400 Subject: Fix clang and mingw tests --- src/engine/SCons/Tool/mingw.py | 7 ++++++- test/import.py | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py index 4121902..de493f0 100644 --- a/src/engine/SCons/Tool/mingw.py +++ b/src/engine/SCons/Tool/mingw.py @@ -156,7 +156,12 @@ def generate(env): env['PROGSUFFIX'] = '.exe' def exists(env): - return find(env) + mingw = SCons.Tool.find_program_path(env, key_program, default_paths=[r'c:\MinGW\bin',]) + if mingw: + mingw_bin_dir = os.path.dirname(mingw) + env.AppendENVPath('PATH', mingw_bin_dir) + + return mingw # Local Variables: # tab-width:4 diff --git a/test/import.py b/test/import.py index b6e5a8d..11fee9c 100644 --- a/test/import.py +++ b/test/import.py @@ -75,6 +75,8 @@ ignore = ('__init__.py', '386asm.py', 'linkloc.py', # Directory of common stuff for MSVC and MSVS 'MSCommon', + # clang common + "clangCommon", # Sun pkgchk and pkginfo common stuff 'sun_pkg.py', # RPM utilities -- cgit v0.12 From 7ad7a9bc0a802096e2a5beadcca8f6a10822ef95 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 25 Mar 2018 13:51:42 -0400 Subject: added more default mingw paths per comments on pull request --- src/engine/SCons/Tool/mingw.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py index de493f0..f2e9442 100644 --- a/src/engine/SCons/Tool/mingw.py +++ b/src/engine/SCons/Tool/mingw.py @@ -43,6 +43,14 @@ import SCons.Tool import SCons.Util +mingw_paths = [ + r'c:\MinGW\bin', + r'C:\cygwin64\bin', + r'C:\msys64', + r'C:\cygwin\bin', + r'C:\msys', +] + def shlib_generator(target, source, env, for_signature): cmd = SCons.Util.CLVar(['$SHLINK', '$SHLINKFLAGS']) @@ -111,7 +119,7 @@ key_program = 'mingw32-gcc' def generate(env): - mingw = SCons.Tool.find_program_path(env, key_program, default_paths=[r'c:\MinGW\bin',]) + mingw = SCons.Tool.find_program_path(env, key_program, default_paths=mingw_paths) if mingw: mingw_bin_dir = os.path.dirname(mingw) env.AppendENVPath('PATH', mingw_bin_dir) @@ -156,7 +164,7 @@ def generate(env): env['PROGSUFFIX'] = '.exe' def exists(env): - mingw = SCons.Tool.find_program_path(env, key_program, default_paths=[r'c:\MinGW\bin',]) + mingw = SCons.Tool.find_program_path(env, key_program, default_paths=mingw_paths) if mingw: mingw_bin_dir = os.path.dirname(mingw) env.AppendENVPath('PATH', mingw_bin_dir) -- cgit v0.12 From 35ac5267d91db9379129fc33d49d6699350347e5 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Mon, 9 Apr 2018 15:34:24 -0500 Subject: updated yacc tool to add default paths, updated to test to use the tool specifically and know when to skip on windows. --- src/engine/SCons/Tool/yacc.py | 9 +++++++++ test/YACC/YACC.py | 8 ++++---- test/YACC/live-check-output-cleaned.py | 2 +- test/YACC/live.py | 12 +++++++++--- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/engine/SCons/Tool/yacc.py b/src/engine/SCons/Tool/yacc.py index 648433b..be65104 100644 --- a/src/engine/SCons/Tool/yacc.py +++ b/src/engine/SCons/Tool/yacc.py @@ -34,6 +34,7 @@ selection method. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os.path +import sys import SCons.Defaults import SCons.Tool @@ -113,6 +114,14 @@ def generate(env): cxx_file.add_action('.yy', YaccAction) cxx_file.add_emitter('.yy', yyEmitter) + if(sys.platform == 'win32'): + bison = SCons.Tool.find_program_path(env, 'bison', default_paths=['C:\\cygwin64\\bin', 'C:\\cygwin\\bin', 'C:\\msys', 'C:\\msys64' ]) + if bison: + bison_bin_dir = os.path.dirname(bison) + env.AppendENVPath('PATH', bison_bin_dir) + else: + SCons.Warnings.Warning('yacc tool requested, but bison binary not found in ENV PATH') + env['YACC'] = env.Detect('bison') or 'yacc' env['YACCFLAGS'] = SCons.Util.CLVar('') env['YACCCOM'] = '$YACC $YACCFLAGS -o $TARGET $SOURCES' diff --git a/test/YACC/YACC.py b/test/YACC/YACC.py index 3fc1f7c..b27c2a7 100644 --- a/test/YACC/YACC.py +++ b/test/YACC/YACC.py @@ -53,10 +53,10 @@ env.CFile(target = 'ddd', source = 'ddd.ym') test.run(arguments = '.', stderr = None) -test.must_match('aaa.c', "aaa.y\nmyyacc.py\n") -test.must_match('bbb.c', "bbb.yacc\nmyyacc.py\n") -test.must_match('ccc.cc', "ccc.yacc\nmyyacc.py\n") -test.must_match('ddd.m', "ddd.yacc\nmyyacc.py\n") +test.must_match('aaa.c', "aaa.y" + os.linesep + "myyacc.py" + os.linesep) +test.must_match('bbb.c', "bbb.yacc" + os.linesep + "myyacc.py" + os.linesep) +test.must_match('ccc.cc', "ccc.yacc" + os.linesep + "myyacc.py" + os.linesep) +test.must_match('ddd.m', "ddd.yacc" + os.linesep + "myyacc.py" + os.linesep) diff --git a/test/YACC/live-check-output-cleaned.py b/test/YACC/live-check-output-cleaned.py index a6240c9..8329b94 100644 --- a/test/YACC/live-check-output-cleaned.py +++ b/test/YACC/live-check-output-cleaned.py @@ -40,7 +40,7 @@ if not yacc: test.skip_test('No yacc or bison found; skipping test.\n') test.write('SConstruct', """ -foo = Environment(YACCFLAGS='-v -d') +foo = Environment(YACCFLAGS='-v -d', tools = ['default', 'yacc']) foo.CFile(source = 'foo.y') """ % locals()) diff --git a/test/YACC/live.py b/test/YACC/live.py index 6dd08f7..35f6c37 100644 --- a/test/YACC/live.py +++ b/test/YACC/live.py @@ -29,6 +29,8 @@ Test YACC and YACCFLAGS with a live yacc compiler. """ import TestSCons +import sys +import os _exe = TestSCons._exe _python_ = TestSCons._python_ @@ -40,12 +42,16 @@ yacc = test.where_is('yacc') or test.where_is('bison') if not yacc: test.skip_test('No yacc or bison found; skipping test.\n') +if sys.platform == 'win32': + if not test.where_is('gcc'): + test.skip_test('No gcc found on windows; skipping test.\n') + test.file_fixture('wrapper.py') test.write('SConstruct', """ -foo = Environment(YACCFLAGS='-d') +foo = Environment(YACCFLAGS='-d', tools = ['default', 'yacc']) yacc = foo.Dictionary('YACC') -bar = Environment(YACC = r'%(_python_)s wrapper.py ' + yacc) +bar = Environment(YACC = r'%(_python_)s wrapper.py ' + yacc, tools = ['default', 'yacc']) foo.Program(target = 'foo', source = 'foo.y') bar.Program(target = 'bar', source = 'bar.y') foo.Program(target = 'hello', source = ['hello.cpp']) @@ -152,7 +158,7 @@ test.run(arguments = 'bar' + _exe) test.up_to_date(arguments = 'bar' + _exe) -test.must_match(test.workpath('wrapper.out'), "wrapper.py\n") +test.must_match(test.workpath('wrapper.out'), "wrapper.py" + os.linesep) test.run(program = test.workpath('bar'), stdin = "b\n", stdout = "bar.y\n") -- cgit v0.12 From de0d6f38404c6880e4b401f7ea6fd629bcb765ea Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Tue, 10 Apr 2018 08:03:43 -0500 Subject: removed binary read and write so newlines are crossplatform and updated path for appveyor script --- .appveyor.yml | 2 +- test/YACC/YACC-fixture/myyacc.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index a9d2dfc..a735070 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -2,7 +2,7 @@ image: Visual Studio 2017 shallow_clone: true install: - - "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + - "set PATH=%PYTHON%;%PYTHON%\\Scripts;C:\\cygwin64\\bin;C:\\msys64;%PATH%" - python --version - pip install lxml - pip install pypiwin32 diff --git a/test/YACC/YACC-fixture/myyacc.py b/test/YACC/YACC-fixture/myyacc.py index c2e1abf..756c98f 100644 --- a/test/YACC/YACC-fixture/myyacc.py +++ b/test/YACC/YACC-fixture/myyacc.py @@ -4,10 +4,10 @@ cmd_opts, args = getopt.getopt(sys.argv[1:], 'o:', []) output = None opt_string = '' for opt, arg in cmd_opts: - if opt == '-o': output = open(arg, 'wb') + if opt == '-o': output = open(arg, 'w') else: opt_string = opt_string + ' ' + opt for a in args: - contents = open(a, 'rb').read() - output.write(contents.replace(b'YACC', b'myyacc.py')) + contents = open(a, 'r').read() + output.write(contents.replace('YACC', 'myyacc.py')) output.close() sys.exit(0) -- cgit v0.12 From 1912634e6d65133f8101043ae1e702a7407d60e3 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Tue, 10 Apr 2018 11:17:40 -0500 Subject: removed unnecessary parans --- src/engine/SCons/Tool/yacc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/SCons/Tool/yacc.py b/src/engine/SCons/Tool/yacc.py index be65104..a43b3b3 100644 --- a/src/engine/SCons/Tool/yacc.py +++ b/src/engine/SCons/Tool/yacc.py @@ -114,7 +114,7 @@ def generate(env): cxx_file.add_action('.yy', YaccAction) cxx_file.add_emitter('.yy', yyEmitter) - if(sys.platform == 'win32'): + if sys.platform == 'win32': bison = SCons.Tool.find_program_path(env, 'bison', default_paths=['C:\\cygwin64\\bin', 'C:\\cygwin\\bin', 'C:\\msys', 'C:\\msys64' ]) if bison: bison_bin_dir = os.path.dirname(bison) -- cgit v0.12 From 3dc6c4e24ec1ed72a386aeb9a791cb67db46a7c2 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Tue, 10 Apr 2018 12:40:36 -0500 Subject: moved default paths for cywing and mingw to their own platform modules. --- src/engine/SCons/Platform/cygwin.py | 9 +++++++++ src/engine/SCons/Platform/mingw.py | 39 +++++++++++++++++++++++++++++++++++++ src/engine/SCons/Tool/yacc.py | 4 +++- 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/engine/SCons/Platform/mingw.py diff --git a/src/engine/SCons/Platform/cygwin.py b/src/engine/SCons/Platform/cygwin.py index 8b4669c..f6c5086 100644 --- a/src/engine/SCons/Platform/cygwin.py +++ b/src/engine/SCons/Platform/cygwin.py @@ -32,9 +32,18 @@ selection method. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import sys + from . import posix from SCons.Platform import TempFileMunge +CYGWIN_DEFAULT_PATHS = [] +if sys.platform == 'win32': + CYGWIN_DEFAULT_PATHS = [ + r'C:\cygwin64\bin', + r'C:\cygwin\bin' + ] + def generate(env): posix.generate(env) diff --git a/src/engine/SCons/Platform/mingw.py b/src/engine/SCons/Platform/mingw.py new file mode 100644 index 0000000..73633d7 --- /dev/null +++ b/src/engine/SCons/Platform/mingw.py @@ -0,0 +1,39 @@ +"""SCons.Platform.mingw + +Platform-specific initialization for the MinGW system. + +""" + +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import sys + +MINGW_DEFAULT_PATHS = [] +if sys.platform == 'win32': + MINGW_DEFAULT_PATHS = [ + r'C:\msys64', + r'C:\msys' + ] \ No newline at end of file diff --git a/src/engine/SCons/Tool/yacc.py b/src/engine/SCons/Tool/yacc.py index a43b3b3..cd9b9a8 100644 --- a/src/engine/SCons/Tool/yacc.py +++ b/src/engine/SCons/Tool/yacc.py @@ -39,6 +39,8 @@ import sys import SCons.Defaults import SCons.Tool import SCons.Util +from SCons.Platform.mingw import MINGW_DEFAULT_PATHS +from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS YaccAction = SCons.Action.Action("$YACCCOM", "$YACCCOMSTR") @@ -115,7 +117,7 @@ def generate(env): cxx_file.add_emitter('.yy', yyEmitter) if sys.platform == 'win32': - bison = SCons.Tool.find_program_path(env, 'bison', default_paths=['C:\\cygwin64\\bin', 'C:\\cygwin\\bin', 'C:\\msys', 'C:\\msys64' ]) + bison = SCons.Tool.find_program_path(env, 'bison', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) if bison: bison_bin_dir = os.path.dirname(bison) env.AppendENVPath('PATH', bison_bin_dir) -- cgit v0.12 From 56471eea9278a5e1bfb92a5214e8c99d2c939a35 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Tue, 10 Apr 2018 16:08:32 -0500 Subject: updated CHANGES.txt --- src/CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index f9cb5c4..58dc6df 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -19,6 +19,8 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - Fixed handling of side-effects in task master (fixes #3013). From Daniel Moody: + - Add common location for default paths for cygwin and mingw in Platform modules + - Updated YACC tool to work on windows with Cygwin/MinGW setups - Set the pickling protocal back to highest which was causing issues with variant dir tests. This will cause issues if reading sconsigns pickled with the previous lower protocal. -- cgit v0.12 From 85d080e18906fb52d425a84e5828189d8a532ce0 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Thu, 12 Apr 2018 15:06:44 -0500 Subject: updated gettext tools to work on windows cygwin and mingw environments. --- src/engine/SCons/Tool/GettextCommon.py | 2 +- src/engine/SCons/Tool/gettext_tool.py | 12 ++++++++++++ src/engine/SCons/Tool/msgfmt.py | 14 ++++++++++++++ src/engine/SCons/Tool/msginit.py | 14 ++++++++++++++ src/engine/SCons/Tool/msgmerge.py | 15 ++++++++++++++- src/engine/SCons/Tool/xgettext.py | 14 +++++++++++++- 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/engine/SCons/Tool/GettextCommon.py b/src/engine/SCons/Tool/GettextCommon.py index 3b840a6..2d37def 100644 --- a/src/engine/SCons/Tool/GettextCommon.py +++ b/src/engine/SCons/Tool/GettextCommon.py @@ -447,7 +447,7 @@ def _detect_msgfmt(env): """ Detects *msgmfmt(1)* program. """ if 'MSGFMT' in env: return env['MSGFMT'] - msgfmt = env.Detect('msgfmt'); + msgfmt = env.Detect('msgfmt') if msgfmt: return msgfmt raise SCons.Errors.StopError(MsgfmtNotFound, "Could not detect msgfmt") diff --git a/src/engine/SCons/Tool/gettext_tool.py b/src/engine/SCons/Tool/gettext_tool.py index 6031e49..41bd70b 100644 --- a/src/engine/SCons/Tool/gettext_tool.py +++ b/src/engine/SCons/Tool/gettext_tool.py @@ -27,10 +27,22 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" ############################################################################# def generate(env,**kw): + import sys + import os import SCons.Tool + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + from SCons.Tool.GettextCommon \ import _translate, tool_list for t in tool_list(env['PLATFORM'], env): + if sys.platform == 'win32': + tool = SCons.Tool.find_program_path(env, t, default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) + if tool: + tool_bin_dir = os.path.dirname(tool) + env.AppendENVPath('PATH', tool_bin_dir) + else: + SCons.Warnings.Warning(t + ' tool requested, but binary not found in ENV PATH') env.Tool(t) env.AddMethod(_translate, 'Translate') ############################################################################# diff --git a/src/engine/SCons/Tool/msgfmt.py b/src/engine/SCons/Tool/msgfmt.py index 4fe6afd..3c9dde7 100644 --- a/src/engine/SCons/Tool/msgfmt.py +++ b/src/engine/SCons/Tool/msgfmt.py @@ -75,8 +75,22 @@ def _create_mo_file_builder(env, **kw): ############################################################################# def generate(env,**kw): """ Generate `msgfmt` tool """ + import sys + import os import SCons.Util + import SCons.Tool from SCons.Tool.GettextCommon import _detect_msgfmt + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + + if sys.platform == 'win32': + msgfmt = SCons.Tool.find_program_path(env, 'msgfmt', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) + if msgfmt: + msgfmt_bin_dir = os.path.dirname(msgfmt) + env.AppendENVPath('PATH', msgfmt_bin_dir) + else: + SCons.Warnings.Warning('msgfmt tool requested, but binary not found in ENV PATH') + try: env['MSGFMT'] = _detect_msgfmt(env) except: diff --git a/src/engine/SCons/Tool/msginit.py b/src/engine/SCons/Tool/msginit.py index 39f460d..9e056e7 100644 --- a/src/engine/SCons/Tool/msginit.py +++ b/src/engine/SCons/Tool/msginit.py @@ -77,8 +77,22 @@ def _POInitBuilderWrapper(env, target=None, source=_null, **kw): ############################################################################# def generate(env,**kw): """ Generate the `msginit` tool """ + import sys + import os import SCons.Util + import SCons.Tool from SCons.Tool.GettextCommon import _detect_msginit + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + + if sys.platform == 'win32': + msginit = SCons.Tool.find_program_path(env, 'msginit', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) + if msginit: + msginit_bin_dir = os.path.dirname(msginit) + env.AppendENVPath('PATH', msginit_bin_dir) + else: + SCons.Warnings.Warning('msginit tool requested, but binary not found in ENV PATH') + try: env['MSGINIT'] = _detect_msginit(env) except: diff --git a/src/engine/SCons/Tool/msgmerge.py b/src/engine/SCons/Tool/msgmerge.py index 11d7b48..76661d2 100644 --- a/src/engine/SCons/Tool/msgmerge.py +++ b/src/engine/SCons/Tool/msgmerge.py @@ -68,8 +68,21 @@ def _POUpdateBuilderWrapper(env, target=None, source=_null, **kw): ############################################################################# def generate(env,**kw): - """ Generate the `xgettext` tool """ + """ Generate the `msgmerge` tool """ + import sys + import os + import SCons.Tool from SCons.Tool.GettextCommon import _detect_msgmerge + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + + if sys.platform == 'win32': + msgmerge = SCons.Tool.find_program_path(env, 'msgmerge', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) + if msgmerge: + msgmerge_bin_dir = os.path.dirname(msgmerge) + env.AppendENVPath('PATH', msgmerge_bin_dir) + else: + SCons.Warnings.Warning('msgmerge tool requested, but binary not found in ENV PATH') try: env['MSGMERGE'] = _detect_msgmerge(env) except: diff --git a/src/engine/SCons/Tool/xgettext.py b/src/engine/SCons/Tool/xgettext.py index 2c0ce40..e4a17ca 100644 --- a/src/engine/SCons/Tool/xgettext.py +++ b/src/engine/SCons/Tool/xgettext.py @@ -288,9 +288,21 @@ def _POTUpdateBuilder(env, **kw): ############################################################################# def generate(env, **kw): """ Generate `xgettext` tool """ + import sys + import os import SCons.Util + import SCons.Tool from SCons.Tool.GettextCommon import RPaths, _detect_xgettext - + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + + if sys.platform == 'win32': + xgettext = SCons.Tool.find_program_path(env, 'xgettext', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS ) + if xgettext: + xgettext_bin_dir = os.path.dirname(xgettext) + env.AppendENVPath('PATH', xgettext_bin_dir) + else: + SCons.Warnings.Warning('xgettext tool requested, but binary not found in ENV PATH') try: env['XGETTEXT'] = _detect_xgettext(env) except: -- cgit v0.12 From f8422a59151aa744c025675a7c4bf3d5e3b8d7c0 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 13 Apr 2018 11:08:56 -0500 Subject: updated CHANGES.txt --- src/CHANGES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 58dc6df..43c1979 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -19,6 +19,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - Fixed handling of side-effects in task master (fixes #3013). From Daniel Moody: + - Updated gettext tools to setup default paths for windows with Cygwin/MinGW setups - Add common location for default paths for cygwin and mingw in Platform modules - Updated YACC tool to work on windows with Cygwin/MinGW setups - Set the pickling protocal back to highest which was causing issues -- cgit v0.12 From 8d41ab0567e38e5d03138056560861343bd9a2a7 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 25 May 2018 11:26:02 -0700 Subject: Handful of changes to get clang tests passing on windows. Use glob to find other possible default mingw install paths. mingw is needed in many cases on windows to compile/archive. --- src/engine/SCons/Tool/clang.py | 3 ++- src/engine/SCons/Tool/mingw.py | 21 ++++++++++++++++++++- test/Clang/clang_default_environment.py | 5 ++--- test/Clang/clang_shared_library.py | 12 +++++++----- test/Clang/clang_specific_environment.py | 1 + test/Clang/clang_static_library.py | 3 ++- test/Clang/clangxx_default_environment.py | 5 ++--- test/Clang/clangxx_shared_library.py | 11 +++++++---- test/Clang/clangxx_specific_environment.py | 3 ++- test/Clang/clangxx_static_library.py | 3 ++- 10 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/engine/SCons/Tool/clang.py b/src/engine/SCons/Tool/clang.py index c51e753..cbb48cb 100644 --- a/src/engine/SCons/Tool/clang.py +++ b/src/engine/SCons/Tool/clang.py @@ -56,7 +56,8 @@ def generate(env): if env['PLATFORM'] == 'win32': # Ensure that we have a proper path for clang - clang = SCons.Tool.find_program_path(env, compilers[0], default_paths=get_clang_install_dirs(env['PLATFORM'])) + clang = SCons.Tool.find_program_path(env, compilers[0], + default_paths=get_clang_install_dirs(env['PLATFORM'])) if clang: clang_bin_dir = os.path.dirname(clang) env.AppendENVPath('PATH', clang_bin_dir) diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py index f2e9442..738460d 100644 --- a/src/engine/SCons/Tool/mingw.py +++ b/src/engine/SCons/Tool/mingw.py @@ -35,6 +35,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os import os.path +import glob import SCons.Action import SCons.Builder @@ -115,10 +116,28 @@ res_builder = SCons.Builder.Builder(action=res_action, suffix='.o', SCons.Tool.SourceFileScanner.add_scanner('.rc', SCons.Defaults.CScan) # This is what we search for to find mingw: -key_program = 'mingw32-gcc' +# key_program = 'mingw32-gcc' +key_program = 'mingw32-make' + + + +def find_version_specific_mingw_paths(): + """ + One example of default mingw install paths is: + C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev2\mingw64\bin + + Use glob'ing to find such and add to mingw_paths + """ + new_paths = glob.glob(r"C:\mingw-w64\*\mingw64\bin") + + return new_paths def generate(env): + global mingw_paths + # Check for reasoanble mingw default paths + mingw_paths +=find_version_specific_mingw_paths() + mingw = SCons.Tool.find_program_path(env, key_program, default_paths=mingw_paths) if mingw: mingw_bin_dir = os.path.dirname(mingw) diff --git a/test/Clang/clang_default_environment.py b/test/Clang/clang_default_environment.py index 4ac1c68..5ebd839 100644 --- a/test/Clang/clang_default_environment.py +++ b/test/Clang/clang_default_environment.py @@ -35,9 +35,8 @@ if not test.where_is('clang'): ## This will likely NOT use clang test.write('SConstruct', """ -env = Environment() -if env['CC'] != 'clang': - env['CC'] = 'clang' +DefaultEnvironment(tools=[]) +env = Environment(tools=['clang','link']) env.Program('foo.c') """) diff --git a/test/Clang/clang_shared_library.py b/test/Clang/clang_shared_library.py index 5e4d36f..9af3770 100644 --- a/test/Clang/clang_shared_library.py +++ b/test/Clang/clang_shared_library.py @@ -33,20 +33,22 @@ test = TestSCons.TestSCons() if not test.where_is('clang'): test.skip_test("Could not find 'clang', skipping test.\n") -platform = Base()['PLATFORM'] +base = Base() +platform = base['PLATFORM'] if platform == 'posix': - filename = 'foo.os' + filename_options = ['foo.os'] libraryname = 'libfoo.so' elif platform == 'darwin': - filename = 'foo.os' + filename_options = ['foo.os'] libraryname = 'libfoo.dylib' elif platform == 'win32': - filename = 'foo.obj' + filename_options = ['foo.obj','foo.os'] libraryname = 'foo.dll' else: test.fail_test() test.write('SConstruct', """\ +DefaultEnvironment(tools=[]) env = Environment(tools=['clang', 'link']) env.SharedLibrary('foo', 'foo.c') """) @@ -59,7 +61,7 @@ int bar() { test.run() -test.must_exist(test.workpath(filename)) +test.must_exist_one_of([test.workpath(f) for f in filename_options]) test.must_exist(test.workpath(libraryname)) test.pass_test() diff --git a/test/Clang/clang_specific_environment.py b/test/Clang/clang_specific_environment.py index 7266a9f..81991f0 100644 --- a/test/Clang/clang_specific_environment.py +++ b/test/Clang/clang_specific_environment.py @@ -33,6 +33,7 @@ if not test.where_is('clang'): test.skip_test("Could not find 'clang', skipping test.\n") test.write('SConstruct', """\ +DefaultEnvironment(tools=[]) env = Environment(tools=['clang', 'link']) env.Program('foo.c') """) diff --git a/test/Clang/clang_static_library.py b/test/Clang/clang_static_library.py index 39e9931..7dedc6f 100644 --- a/test/Clang/clang_static_library.py +++ b/test/Clang/clang_static_library.py @@ -33,7 +33,8 @@ if not test.where_is('clang'): test.skip_test("Could not find 'clang', skipping test.\n") test.write('SConstruct', """\ -env = Environment(tools=['clang', 'ar']) +DefaultEnvironment(tools=[]) +env = Environment(tools=['mingw','clang', 'ar']) env.StaticLibrary('foo', 'foo.c') """) diff --git a/test/Clang/clangxx_default_environment.py b/test/Clang/clangxx_default_environment.py index beef1e5..5e46404 100644 --- a/test/Clang/clangxx_default_environment.py +++ b/test/Clang/clangxx_default_environment.py @@ -35,9 +35,8 @@ if not test.where_is('clang'): ## This will likely NOT use clang++. test.write('SConstruct', """\ -env = Environment() -if env['CXX'] != 'clang++': - env['CXX'] = 'clang++' +DefaultEnvironment(tools=[]) +env = Environment(tools=['clangxx','link']) env.Program('foo.cpp') """) diff --git a/test/Clang/clangxx_shared_library.py b/test/Clang/clangxx_shared_library.py index d6337ba..6240299 100644 --- a/test/Clang/clangxx_shared_library.py +++ b/test/Clang/clangxx_shared_library.py @@ -36,18 +36,21 @@ if not test.where_is('clang'): platform = Base()['PLATFORM'] if platform == 'posix': - filename = 'foo.os' + filename_options = ['foo.os'] libraryname = 'libfoo.so' elif platform == 'darwin': - filename = 'foo.os' + filename_options = ['foo.os'] libraryname = 'libfoo.dylib' elif platform == 'win32': - filename = 'foo.obj' + filename_options = ['foo.obj','foo.os'] libraryname = 'foo.dll' else: test.fail_test() + + test.write('SConstruct', """\ +DefaultEnvironment(tools=[]) env = Environment(tools=['clang++', 'link']) env.SharedLibrary('foo', 'foo.cpp') """) @@ -60,7 +63,7 @@ int bar() { test.run() -test.must_exist(test.workpath(filename)) +test.must_exist_one_of([test.workpath(f) for f in filename_options]) test.must_exist(test.workpath(libraryname)) test.pass_test() diff --git a/test/Clang/clangxx_specific_environment.py b/test/Clang/clangxx_specific_environment.py index 773fa94..35540bb 100644 --- a/test/Clang/clangxx_specific_environment.py +++ b/test/Clang/clangxx_specific_environment.py @@ -33,7 +33,8 @@ if not test.where_is('clang'): test.skip_test("Could not find 'clang++', skipping test.\n") test.write('SConstruct', """\ -env = Environment(tools=['clang++', 'link']) +DefaultEnvironment(tools=[]) +env = Environment(tools=['mingw','clang++', 'link']) env.Program('foo.cpp') """) diff --git a/test/Clang/clangxx_static_library.py b/test/Clang/clangxx_static_library.py index 77ea58e..c768deb 100644 --- a/test/Clang/clangxx_static_library.py +++ b/test/Clang/clangxx_static_library.py @@ -33,7 +33,8 @@ if not test.where_is('clang'): test.skip_test("Could not find 'clang++', skipping test.\n") test.write('SConstruct', """\ -env = Environment(tools=['clang++', 'ar']) +DefaultEnvironment(tools=[]) +env = Environment(tools=['mingw','clang++', 'ar']) env.StaticLibrary('foo', 'foo.cpp') """) -- cgit v0.12 From b86c5845efccdec97dd267efe6f61c23db92c5a9 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 25 May 2018 12:45:47 -0700 Subject: rebase to upstream/master --- src/CHANGES.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 43c1979..75133e8 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -44,6 +44,13 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - Re-Enable parallel SCons (-j) when running via Pypy - Move SCons test framework files to testing/framework and remove all references to QMtest. QMTest has not been used by SCons for some time now. + - Updated logic for mingw and clang on win32 to search default tool install paths if not + found in normal SCons PATH. If the user specifies PATH or tool specific paths they + will be used and the default paths below will be ignored. + - Default path for clang/clangxx : C:\Program Files\LLVM\bin + - Default path for mingw : C:\MinGW\bin and/or C:\mingw-w64\*\mingw64\bin + - Key program to locate mingw : mingw32-make (as the gcc with mingw prefix has no fixed name) + From Andrew Featherstone - Removed unused --warn options from the man page and source code. -- cgit v0.12 From cf74ad1fc7b745f8dfcf5886aa5e0326d104caa0 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 25 May 2018 12:48:45 -0700 Subject: remove mingw tool as it's breaking non win tests --- test/Clang/clangxx_specific_environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Clang/clangxx_specific_environment.py b/test/Clang/clangxx_specific_environment.py index 35540bb..39eaab1 100644 --- a/test/Clang/clangxx_specific_environment.py +++ b/test/Clang/clangxx_specific_environment.py @@ -34,7 +34,7 @@ if not test.where_is('clang'): test.write('SConstruct', """\ DefaultEnvironment(tools=[]) -env = Environment(tools=['mingw','clang++', 'link']) +env = Environment(tools=['clang++', 'link']) env.Program('foo.cpp') """) -- cgit v0.12 From 0e5af237a0eff7c49fb354697f348e7f2b0e629e Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 25 May 2018 15:07:11 -0500 Subject: pr-3052: Updated swig tool with some default paths and fixed test to build on windows. --- .appveyor.yml | 8 +++---- src/engine/SCons/Tool/swig.py | 12 ++++++++++ test/SWIG/recursive-includes-cpp.py | 44 +++++++++++++++++++++++++++---------- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index a735070..fe231d2 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,10 +6,10 @@ install: - python --version - pip install lxml - pip install pypiwin32 - - choco install dmd - - choco install ldc - - choco install swig - - choco install vswhere + - choco install --allow-empty-checksums dmd + - choco install --allow-empty-checksums ldc + - choco install --allow-empty-checksums swig + - choco install --allow-empty-checksums vswhere environment: matrix: diff --git a/src/engine/SCons/Tool/swig.py b/src/engine/SCons/Tool/swig.py index da4472f..08881a0 100644 --- a/src/engine/SCons/Tool/swig.py +++ b/src/engine/SCons/Tool/swig.py @@ -35,6 +35,7 @@ from __future__ import print_function __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os.path +import sys import re import subprocess @@ -169,6 +170,17 @@ def generate(env): java_file.add_action('.i', SwigAction) java_file.add_emitter('.i', _swigEmitter) + from SCons.Platform.mingw import MINGW_DEFAULT_PATHS + from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS + + if sys.platform == 'win32': + swig = SCons.Tool.find_program_path(env, 'swig', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS + [r'C:\ProgramData\chocolatey\bin'] ) + if swig: + swig_bin_dir = os.path.dirname(swig) + env.AppendENVPath('PATH', swig_bin_dir) + else: + SCons.Warnings.Warning('swig tool requested, but binary not found in ENV PATH') + if 'SWIG' not in env: env['SWIG'] = env.Detect(swigs) or swigs[0] env['SWIGVERSION'] = _get_swig_version(env, env['SWIG']) diff --git a/test/SWIG/recursive-includes-cpp.py b/test/SWIG/recursive-includes-cpp.py index e69b5b9..2767c4b 100644 --- a/test/SWIG/recursive-includes-cpp.py +++ b/test/SWIG/recursive-includes-cpp.py @@ -30,6 +30,7 @@ in cases of recursive inclusion. """ import os +import sys import TestSCons from SCons.Defaults import DefaultEnvironment @@ -42,6 +43,11 @@ for pre_req in ['swig', 'python']: if not test.where_is(pre_req): test.skip_test('Can not find installed "' + pre_req + '", skipping test.%s' % os.linesep) +if sys.platform == 'win32': + python_lib = os.path.dirname(sys.executable) + "/libs/" + ('python%d%d'%(sys.version_info[0],sys.version_info[1])) + '.lib' + if( not os.path.isfile(python_lib)): + test.skip_test('Can not find python lib at "' + python_lib + '", skipping test.%s' % os.linesep) + test.write("recursive.h", """\ /* An empty header file. */ """) @@ -62,24 +68,34 @@ test.write("mod.i", """\ #include "main.h" """) +if sys.platform == 'win32': + if(sys.maxsize > 2**32): + TARGET_ARCH = 'x86_64' + else: + TARGET_ARCH = 'x86' + test.write('SConstruct', """\ import distutils.sysconfig import sys - -DefaultEnvironment( tools = [ 'swig' ] ) +import os env = Environment( + TARGET_ARCH = '""" + TARGET_ARCH +"""' SWIGFLAGS = [ '-python' ], CPPPATH = [ distutils.sysconfig.get_python_inc() ], - SHLIBPREFIX = "" + SHLIBPREFIX = "", + tools = [ 'default', 'swig' ] ) if sys.platform == 'darwin': env['LIBS']=['python%d.%d'%(sys.version_info[0],sys.version_info[1])] +elif sys.platform == 'win32': + env.Append(LIBS=['python%d%d'%(sys.version_info[0],sys.version_info[1])]) + env.Append(LIBPATH=[os.path.dirname(sys.executable) + "/libs"]) env.SharedLibrary( 'mod', @@ -90,28 +106,34 @@ env.SharedLibrary( ) """) +if sys.platform == 'win32': + object_suffix = ".obj" +else: + object_suffix = ".os" + expectMain = """\ -+-main.os ++-main%s +-main.c +-main.h - +-recursive.h""" + +-recursive.h""" % object_suffix expectMod = """\ -+-mod_wrap.os ++-mod_wrap%s +-mod_wrap.c | +-mod.i | +-main.h - | +-recursive.h""" + | +-recursive.h""" % object_suffix # Validate that the recursive dependencies are found with SWIG scanning first. -test.run( arguments = '--tree=all mod_wrap.os main.os' ) - +test.run( arguments = '--tree=all mod_wrap'+object_suffix +' main'+object_suffix) +print(test.stdout()) test.must_contain_all( test.stdout(), expectMain ) test.must_contain_all( test.stdout(), expectMod ) -# Validate that the recursive dependencies are found consistently. -test.run( arguments = '--tree=all main.os mod_wrap.os' ) +# Validate that the recursive dependencies are found consistently. +test.run( arguments = '--tree=all main'+object_suffix +' mod_wrap'+object_suffix) +print(test.stdout()) test.must_contain_all( test.stdout(), expectMain ) test.must_contain_all( test.stdout(), expectMod ) -- cgit v0.12 From b20299cf0bbc8b7073838c225d68909d6f021fab Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 25 May 2018 15:16:10 -0500 Subject: pr-3052: added a missing comma to the environment args --- test/SWIG/recursive-includes-cpp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/SWIG/recursive-includes-cpp.py b/test/SWIG/recursive-includes-cpp.py index 2767c4b..2f2cff2 100644 --- a/test/SWIG/recursive-includes-cpp.py +++ b/test/SWIG/recursive-includes-cpp.py @@ -80,7 +80,7 @@ import sys import os env = Environment( - TARGET_ARCH = '""" + TARGET_ARCH +"""' + TARGET_ARCH = '""" + TARGET_ARCH +"""', SWIGFLAGS = [ '-python' ], -- cgit v0.12 From 0f83c3918a0acf84588902c6c3d3f28e764edb56 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 25 May 2018 15:20:02 -0500 Subject: pr-3052: removed debug print statements --- test/SWIG/recursive-includes-cpp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/SWIG/recursive-includes-cpp.py b/test/SWIG/recursive-includes-cpp.py index 2f2cff2..2494c45 100644 --- a/test/SWIG/recursive-includes-cpp.py +++ b/test/SWIG/recursive-includes-cpp.py @@ -126,14 +126,14 @@ expectMod = """\ # Validate that the recursive dependencies are found with SWIG scanning first. test.run( arguments = '--tree=all mod_wrap'+object_suffix +' main'+object_suffix) -print(test.stdout()) + test.must_contain_all( test.stdout(), expectMain ) test.must_contain_all( test.stdout(), expectMod ) # Validate that the recursive dependencies are found consistently. test.run( arguments = '--tree=all main'+object_suffix +' mod_wrap'+object_suffix) -print(test.stdout()) + test.must_contain_all( test.stdout(), expectMain ) test.must_contain_all( test.stdout(), expectMod ) -- cgit v0.12 From cd53a8f8f59ac6d0400d21044c09c50e92792927 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 25 May 2018 15:25:08 -0500 Subject: pr-3052: made target arch transparent to non windows platforms --- test/SWIG/recursive-includes-cpp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/SWIG/recursive-includes-cpp.py b/test/SWIG/recursive-includes-cpp.py index 2494c45..738b6c1 100644 --- a/test/SWIG/recursive-includes-cpp.py +++ b/test/SWIG/recursive-includes-cpp.py @@ -70,9 +70,9 @@ test.write("mod.i", """\ if sys.platform == 'win32': if(sys.maxsize > 2**32): - TARGET_ARCH = 'x86_64' + TARGET_ARCH = "TARGET_ARCH = 'x86_64'," else: - TARGET_ARCH = 'x86' + TARGET_ARCH = "TARGET_ARCH = 'x86'," test.write('SConstruct', """\ import distutils.sysconfig @@ -80,7 +80,7 @@ import sys import os env = Environment( - TARGET_ARCH = '""" + TARGET_ARCH +"""', + """ + TARGET_ARCH + """ SWIGFLAGS = [ '-python' ], -- cgit v0.12 From d1ad2461b0c9f8a9a19f7509b8334d46bf9f3391 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 25 May 2018 15:26:39 -0500 Subject: pr-3052: Needed to define target arch in the non-windows case --- test/SWIG/recursive-includes-cpp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/SWIG/recursive-includes-cpp.py b/test/SWIG/recursive-includes-cpp.py index 738b6c1..8fbe7c0 100644 --- a/test/SWIG/recursive-includes-cpp.py +++ b/test/SWIG/recursive-includes-cpp.py @@ -68,12 +68,14 @@ test.write("mod.i", """\ #include "main.h" """) + if sys.platform == 'win32': if(sys.maxsize > 2**32): TARGET_ARCH = "TARGET_ARCH = 'x86_64'," else: TARGET_ARCH = "TARGET_ARCH = 'x86'," - +else: + TARGET_ARCH = "" test.write('SConstruct', """\ import distutils.sysconfig import sys -- cgit v0.12 From 2cfbdb0e6ce2d1856c3700f4317a6b2242b36851 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 25 May 2018 15:28:23 -0500 Subject: pr-3052: added changes to CHANGES.txt --- src/CHANGES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index c108378..17ca170 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -19,6 +19,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - Fixed handling of side-effects in task master (fixes #3013). From Daniel Moody: + - Updated swig to setup default paths for windows - Updated gettext tools to setup default paths for windows with Cygwin/MinGW setups - Add common location for default paths for cygwin and mingw in Platform modules - Updated YACC tool to work on windows with Cygwin/MinGW setups -- cgit v0.12 From ea6fc1fcb2de8a99a7f8a68d51f5969cdc729d2c Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 25 May 2018 16:30:38 -0500 Subject: pr-3052: fixed some missing quotes for some tests that use the new default java tests for windows --- test/Repository/Java.py | 2 +- test/Repository/JavaH.py | 8 ++++---- test/Repository/RMIC.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/Repository/Java.py b/test/Repository/Java.py index fce85cd..290d742 100644 --- a/test/Repository/Java.py +++ b/test/Repository/Java.py @@ -59,7 +59,7 @@ opts = '-Y ' + test.workpath('rep1') # test.write(['rep1', 'SConstruct'], """ env = Environment(tools = ['javac'], - JAVAC = r'%s') + JAVAC = r'"%s"') env.Java(target = 'classes', source = 'src') """ % javac) diff --git a/test/Repository/JavaH.py b/test/Repository/JavaH.py index ee196cf..1cb5078 100644 --- a/test/Repository/JavaH.py +++ b/test/Repository/JavaH.py @@ -63,8 +63,8 @@ opts = '-Y ' + test.workpath('rep1') # test.write(['rep1', 'SConstruct'], """ env = Environment(tools = ['javac', 'javah'], - JAVAC = r'%s', - JAVAH = r'%s') + JAVAC = r'"%s"', + JAVAH = r'"%s"') classes = env.Java(target = 'classes', source = 'src') env.JavaH(target = 'outdir', source = classes) """ % (javac, javah)) @@ -207,8 +207,8 @@ test.up_to_date(chdir = 'work2', options = opts, arguments = ".") # test.write(['work3', 'SConstruct'], """ env = Environment(tools = ['javac', 'javah'], - JAVAC = r'%s', - JAVAH = r'%s') + JAVAC = r'"%s"', + JAVAH = r'"%s"') classes = env.Java(target = 'classes', source = 'src') hfiles = env.JavaH(target = 'outdir', source = classes) Local(hfiles) diff --git a/test/Repository/RMIC.py b/test/Repository/RMIC.py index 886ccdb..392e6ef 100644 --- a/test/Repository/RMIC.py +++ b/test/Repository/RMIC.py @@ -82,8 +82,8 @@ opts = '-Y ' + test.workpath('rep1') # test.write(['rep1', 'SConstruct'], """ env = Environment(tools = ['javac', 'rmic'], - JAVAC = r'%s', - RMIC = r'%s') + JAVAC = r'"%s"', + RMIC = r'"%s"') classes = env.Java(target = 'classes', source = 'src') # Brute-force removal of the "Hello" class. classes = [c for c in classes if str(c).find('Hello') == -1] @@ -350,8 +350,8 @@ test.up_to_date(chdir = 'work2', options = opts, arguments = ".") # test.write(['work3', 'SConstruct'], """ env = Environment(tools = ['javac', 'rmic'], - JAVAC = r'%s', - RMIC = r'%s') + JAVAC = r'"%s"', + RMIC = r'"%s"') classes = env.Java(target = 'classes', source = 'src') # Brute-force removal of the "Hello" class. classes = [c for c in classes if str(c).find('Hello') == -1] -- cgit v0.12 From 510f8bdfea8338856216e99329692d42fbdfc469 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 25 May 2018 16:43:06 -0500 Subject: pr-3052: updated the expected result to work for windows --- test/Repository/RMIC.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Repository/RMIC.py b/test/Repository/RMIC.py index 392e6ef..e08c716 100644 --- a/test/Repository/RMIC.py +++ b/test/Repository/RMIC.py @@ -29,6 +29,7 @@ Test building Java applications when using Repositories. """ import TestSCons +import os python = TestSCons.python @@ -302,7 +303,7 @@ public class Foo2 extends UnicastRemoteObject implements Hello { test.run(chdir = 'work1', options = opts, arguments = ".") expect = [ - ' src/Foo1.java src/Foo2.java', + ' src' + os.sep + 'Foo1.java src' + os.sep + 'Foo2.java', ' com.sub.foo.Foo1 com.sub.foo.Foo2', ] -- cgit v0.12 From df543ac63394c2dffd50264d188fa3a237f69bc8 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Fri, 25 May 2018 17:06:08 -0500 Subject: pr-3052: moved some 64 bit logic to TestCmd and used TestCmd system checks --- test/SWIG/recursive-includes-cpp.py | 5 +++-- testing/framework/TestCmd.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/SWIG/recursive-includes-cpp.py b/test/SWIG/recursive-includes-cpp.py index 8fbe7c0..2c74cc8 100644 --- a/test/SWIG/recursive-includes-cpp.py +++ b/test/SWIG/recursive-includes-cpp.py @@ -32,6 +32,7 @@ in cases of recursive inclusion. import os import sys import TestSCons +import TestCmd from SCons.Defaults import DefaultEnvironment DefaultEnvironment( tools = [ 'swig' ] ) @@ -69,8 +70,8 @@ test.write("mod.i", """\ """) -if sys.platform == 'win32': - if(sys.maxsize > 2**32): +if TestCmd.IS_WINDOWS: + if TestCmd.IS_64_BIT: TARGET_ARCH = "TARGET_ARCH = 'x86_64'," else: TARGET_ARCH = "TARGET_ARCH = 'x86'," diff --git a/testing/framework/TestCmd.py b/testing/framework/TestCmd.py index 0aab9a8..9cd6b39 100644 --- a/testing/framework/TestCmd.py +++ b/testing/framework/TestCmd.py @@ -309,7 +309,7 @@ import types IS_PY3 = sys.version_info[0] == 3 IS_WINDOWS = sys.platform == 'win32' - +IS_64_BIT = sys.maxsize > 2**32 class null(object): pass -- cgit v0.12 From 18e0da6a3110f2a291b38dc174053acfbbd688ff Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 25 May 2018 22:58:17 -0400 Subject: pr-3052: updated pkg-config test to handle backslashes and spaces on windows --- test/CPPDEFINES/pkg-config.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/CPPDEFINES/pkg-config.py b/test/CPPDEFINES/pkg-config.py index 4096934..8ec98ea 100644 --- a/test/CPPDEFINES/pkg-config.py +++ b/test/CPPDEFINES/pkg-config.py @@ -35,6 +35,8 @@ test = TestSCons.TestSCons() pkg_config_path = test.where_is('pkg-config') if not pkg_config_path: test.skip_test("Could not find 'pkg-config' in system PATH, skipping test.\n") + +pkg_config_path.replace("\\", "/") test.write('bug.pc', """\ prefix=/usr @@ -73,7 +75,7 @@ class OrderedPrintingDict(OrderedDict): # https://github.com/SCons/scons/issues/2671 # Passing test cases env_1 = Environment(CPPDEFINES=[('DEBUG','1'), 'TEST']) -env_1.ParseConfig('PKG_CONFIG_PATH=. %(pkg_config_path)s --cflags bug') +env_1.ParseConfig('PKG_CONFIG_PATH=. "%(pkg_config_path)s" --cflags bug') print(env_1.subst('$_CPPDEFFLAGS')) env_2 = Environment(CPPDEFINES=[('DEBUG','1'), 'TEST']) @@ -82,7 +84,7 @@ print(env_2.subst('$_CPPDEFFLAGS')) # Failing test cases env_3 = Environment(CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None)])) -env_3.ParseConfig('PKG_CONFIG_PATH=. %(pkg_config_path)s --cflags bug') +env_3.ParseConfig('PKG_CONFIG_PATH=. "%(pkg_config_path)s" --cflags bug') print(env_3.subst('$_CPPDEFFLAGS')) env_4 = Environment(CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None)])) @@ -91,7 +93,7 @@ print(env_4.subst('$_CPPDEFFLAGS')) # https://github.com/SCons/scons/issues/1738 env_1738_1 = Environment(tools=['default']) -env_1738_1.ParseConfig('PKG_CONFIG_PATH=. %(pkg_config_path)s --cflags --libs bug') +env_1738_1.ParseConfig('PKG_CONFIG_PATH=. "%(pkg_config_path)s" --cflags --libs bug') env_1738_1.Append(CPPDEFINES={'value' : '1'}) print(env_1738_1.subst('$_CPPDEFFLAGS')) """%locals() ) -- cgit v0.12 From 9de87b96f01a3d9c6b2f731afec856b40c8c6a9e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 26 May 2018 01:22:19 -0400 Subject: pr-3052: updated pkg-config test to work on windows --- test/CPPDEFINES/pkg-config.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/test/CPPDEFINES/pkg-config.py b/test/CPPDEFINES/pkg-config.py index 8ec98ea..65210ce 100644 --- a/test/CPPDEFINES/pkg-config.py +++ b/test/CPPDEFINES/pkg-config.py @@ -29,14 +29,14 @@ Verify merging with MergeFlags to CPPPDEFINES with various data types. """ import TestSCons +import TestCmd test = TestSCons.TestSCons() pkg_config_path = test.where_is('pkg-config') if not pkg_config_path: test.skip_test("Could not find 'pkg-config' in system PATH, skipping test.\n") - -pkg_config_path.replace("\\", "/") +pkg_config_path = pkg_config_path.replace("\\", "/") test.write('bug.pc', """\ prefix=/usr @@ -57,7 +57,18 @@ int main(int argc, char *argv[]) } """) +if TestCmd.IS_WINDOWS: + pkg_config_file = 'bug.pc' + pkg_config_tools = 'mingw' + pkg_config_cl_path = "" +else: + pkg_config_file = 'bug' + pkg_config_tools = 'default' + pkg_config_cl_path = "PKG_CONFIG_PATH=." + test.write('SConstruct', """\ +import os +import sys # Python3 dicts dont preserve order. Hence we supply subclass of OrderedDict # whose __str__ and __repr__ act like a normal dict. from collections import OrderedDict @@ -74,26 +85,28 @@ class OrderedPrintingDict(OrderedDict): """ + """ # https://github.com/SCons/scons/issues/2671 # Passing test cases -env_1 = Environment(CPPDEFINES=[('DEBUG','1'), 'TEST']) -env_1.ParseConfig('PKG_CONFIG_PATH=. "%(pkg_config_path)s" --cflags bug') +env_1 = Environment(CPPDEFINES=[('DEBUG','1'), 'TEST'], tools = ['%(pkg_config_tools)s']) +if sys.platform == 'win32': + os.environ['PKG_CONFIG_PATH'] = env_1.Dir('.').abspath.replace("\\\\" , "/") +env_1.ParseConfig('%(pkg_config_cl_path)s "%(pkg_config_path)s" --cflags %(pkg_config_file)s') print(env_1.subst('$_CPPDEFFLAGS')) -env_2 = Environment(CPPDEFINES=[('DEBUG','1'), 'TEST']) +env_2 = Environment(CPPDEFINES=[('DEBUG','1'), 'TEST'], tools = ['%(pkg_config_tools)s']) env_2.MergeFlags('-DSOMETHING -DVARIABLE=2') print(env_2.subst('$_CPPDEFFLAGS')) # Failing test cases -env_3 = Environment(CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None)])) -env_3.ParseConfig('PKG_CONFIG_PATH=. "%(pkg_config_path)s" --cflags bug') +env_3 = Environment(CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None)]), tools = ['%(pkg_config_tools)s']) +env_3.ParseConfig('%(pkg_config_cl_path)s "%(pkg_config_path)s" --cflags %(pkg_config_file)s') print(env_3.subst('$_CPPDEFFLAGS')) -env_4 = Environment(CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None)])) +env_4 = Environment(CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None)]), tools = ['%(pkg_config_tools)s']) env_4.MergeFlags('-DSOMETHING -DVARIABLE=2') print(env_4.subst('$_CPPDEFFLAGS')) # https://github.com/SCons/scons/issues/1738 -env_1738_1 = Environment(tools=['default']) -env_1738_1.ParseConfig('PKG_CONFIG_PATH=. "%(pkg_config_path)s" --cflags --libs bug') +env_1738_1 = Environment(tools = ['%(pkg_config_tools)s']) +env_1738_1.ParseConfig('%(pkg_config_cl_path)s "%(pkg_config_path)s" --cflags --libs %(pkg_config_file)s') env_1738_1.Append(CPPDEFINES={'value' : '1'}) print(env_1738_1.subst('$_CPPDEFFLAGS')) """%locals() ) -- cgit v0.12 From 4850f5bbc3761b3c84f2788e00968d990acd7e4c Mon Sep 17 00:00:00 2001 From: maiphi Date: Sat, 26 May 2018 16:13:15 +0200 Subject: Added missing FORTRANMODDIRPREFIX=-J to the gfortran tool. Also added a test which compiles a .f90 source file to an executable with gfortran and checks that module files are place in FORTRANMODDID correctly. --- src/CHANGES.txt | 1 + src/engine/SCons/Tool/gfortran.py | 2 + test/Fortran/gfortran.py | 77 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 test/Fortran/gfortran.py diff --git a/src/CHANGES.txt b/src/CHANGES.txt index f9cb5c4..0544f60 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -10,6 +10,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From Philipp Maierhöfer - Added a __hash__ method to the class Scons.Subst.Literal. Required when substituting Literal objects when SCons runs with Python 3. + - Added missing FORTRANMODDIRPREFIX to the gfortran tool. From Richard West: - Add SConstruct.py, Sconstruct.py, sconstruct.py to the search path for the root SConstruct file. diff --git a/src/engine/SCons/Tool/gfortran.py b/src/engine/SCons/Tool/gfortran.py index d33bf52..45750d6 100644 --- a/src/engine/SCons/Tool/gfortran.py +++ b/src/engine/SCons/Tool/gfortran.py @@ -54,6 +54,8 @@ def generate(env): env['INC%sPREFIX' % dialect] = "-I" env['INC%sSUFFIX' % dialect] = "" + env['FORTRANMODDIRPREFIX'] = "-J" + def exists(env): return env.Detect('gfortran') diff --git a/test/Fortran/gfortran.py b/test/Fortran/gfortran.py new file mode 100644 index 0000000..deab52c --- /dev/null +++ b/test/Fortran/gfortran.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that the gfortran tool compiles a .f90 file to an executable, +placing module files in the directory specified by FORTRANMODDIR. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +_exe = TestSCons._exe + +gfortran = test.detect_tool('gfortran') + +if not gfortran: + test.skip_test("Could not find gfortran tool, skipping test.\n") + +test.write('SConstruct', """ +env = Environment(tools=['gfortran','link'], F90PATH='modules', FORTRANMODDIR='modules') +env.Program('test1', 'test1.f90') +""") + +test.write('test1.f90', """\ +module test1mod + implicit none + contains + subroutine hello + implicit none + print *, "hello" + end subroutine hello +end module test1mod +program main + use test1mod + implicit none + call hello() +end program main +""") + +test.run(arguments = '.') + +test.must_exist('test1' + _exe) +test.must_exist(['modules', 'test1mod.mod']) + +test.up_to_date(arguments = '.') + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: -- cgit v0.12 From 0b25163bccfe740a7752bc0ed2d8f8fbe020580f Mon Sep 17 00:00:00 2001 From: maiphi Date: Sun, 27 May 2018 00:10:47 +0200 Subject: Added a gfortran test without FORTRANMODDIR set. --- test/Fortran/gfortran.py | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/test/Fortran/gfortran.py b/test/Fortran/gfortran.py index deab52c..c3dec97 100644 --- a/test/Fortran/gfortran.py +++ b/test/Fortran/gfortran.py @@ -26,7 +26,8 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Verify that the gfortran tool compiles a .f90 file to an executable, -placing module files in the directory specified by FORTRANMODDIR. +one time with and one time without placing module files in a subdirectory +specified by FORTRANMODDIR. """ import TestSCons @@ -41,7 +42,7 @@ if not gfortran: test.skip_test("Could not find gfortran tool, skipping test.\n") test.write('SConstruct', """ -env = Environment(tools=['gfortran','link'], F90PATH='modules', FORTRANMODDIR='modules') +env = Environment(tools=['gfortran','link']) env.Program('test1', 'test1.f90') """) @@ -64,7 +65,36 @@ end program main test.run(arguments = '.') test.must_exist('test1' + _exe) -test.must_exist(['modules', 'test1mod.mod']) +test.must_exist('test1mod.mod') + +test.up_to_date(arguments = '.') + + +test.write('SConstruct', """ +env = Environment(tools=['gfortran','link'], F90PATH='modules', FORTRANMODDIR='modules') +env.Program('test2', 'test2.f90') +""") + +test.write('test2.f90', """\ +module test2mod + implicit none + contains + subroutine hello + implicit none + print *, "hello" + end subroutine hello +end module test2mod +program main + use test2mod + implicit none + call hello() +end program main +""") + +test.run(arguments = '.') + +test.must_exist('test2' + _exe) +test.must_exist(['modules', 'test2mod.mod']) test.up_to_date(arguments = '.') -- cgit v0.12 From e18cc8783f671eb696686887db5674a22f050814 Mon Sep 17 00:00:00 2001 From: maiphi Date: Sun, 27 May 2018 12:04:45 +0200 Subject: Make test FORTRANMODDIR.py work with non-empty FORTRANMODDIRPREFIX. --- test/Fortran/FORTRANMODDIR.py | 68 +++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/test/Fortran/FORTRANMODDIR.py b/test/Fortran/FORTRANMODDIR.py index 723efaf..e6f2413 100644 --- a/test/Fortran/FORTRANMODDIR.py +++ b/test/Fortran/FORTRANMODDIR.py @@ -24,6 +24,16 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +""" +Verify the following things: +* _FORTRANMODFLAG is correctly constructed from FORTRANMODDIRPREFIX and + FORTRANMODDIR. +* The dependency scanner does not expect a module file to be created + from a "module procedure" statement. +* The dependency scanner expects the module files to be created in the correct + module directory (is is verified by the test.up_to_date()). +""" + import TestSCons _python_ = TestSCons._python_ @@ -31,61 +41,59 @@ _exe = TestSCons._exe test = TestSCons.TestSCons() - - test.write('myfortran.py', r""" import os.path import re import sys +# case insensitive matching, because Fortran is case insensitive mod_regex = "(?im)^\\s*MODULE\\s+(?!PROCEDURE)(\\w+)" contents = open(sys.argv[2]).read() modules = re.findall(mod_regex, contents) -modules = [os.path.join(sys.argv[1], m.lower()+'.mod') for m in modules] +(prefix, moddir) = sys.argv[1].split('=') +if prefix != 'moduledir': + sys.exit(1) +modules = [os.path.join(moddir, m.lower()+'.mod') for m in modules] for t in sys.argv[3:] + modules: open(t, 'wb').write(('myfortran.py wrote %s\n' % os.path.split(t)[1]).encode()) -sys.exit(0) """) test.write('SConstruct', """ -env = Environment(FORTRANCOM = r'%(_python_)s myfortran.py $FORTRANMODDIR $SOURCE $TARGET', - FORTRANMODDIR = 'modules') +env = Environment(FORTRANCOM = r'%(_python_)s myfortran.py $_FORTRANMODFLAG $SOURCE $TARGET', + FORTRANMODDIRPREFIX='moduledir=', FORTRANMODDIR='modules') env.Object(target = 'test1.obj', source = 'test1.f') env.Object(target = 'sub2/test2.obj', source = 'test1.f', FORTRANMODDIR='${TARGET.dir}') env.Object(target = 'sub3/test3.obj', source = 'test1.f', - FORTRANCOM = r'%(_python_)s myfortran.py $_FORTRANMODFLAG $SOURCE $TARGET', + FORTRANCOM = r'%(_python_)s myfortran.py moduledir=$FORTRANMODDIR $SOURCE $TARGET', FORTRANMODDIR='${TARGET.dir}') """ % locals()) test.write('test1.f', """\ - PROGRAM TEST - USE MOD_FOO - USE MOD_BAR - PRINT *,'TEST.f' - CALL P - STOP - END - MODULE MOD_FOO - IMPLICIT NONE - CONTAINS - SUBROUTINE P - PRINT *,'mod_foo' - END SUBROUTINE P - END MODULE MOD_FOO - MODULE PROCEDURE MOD_BAR - IMPLICIT NONE - CONTAINS - SUBROUTINE P - PRINT *,'mod_bar' - END SUBROUTINE P - END MODULE MOD_BAR +module mod_foo + implicit none + interface q + module procedure p + end interface q + contains + subroutine p + implicit none + print *, 'mod_foo::p' + end subroutine p +end module mod_foo +program test + use mod_foo + implicit none + print *, 'test.f' + call p + call q +end """) test.run(arguments = '.', stderr = None) test.must_match('test1.obj', "myfortran.py wrote test1.obj\n") test.must_match(['modules', 'mod_foo.mod'], "myfortran.py wrote mod_foo.mod\n") -test.must_not_exist(['modules', 'mod_bar.mod']) +test.must_not_exist(['modules', 'p.mod']) test.must_match(['sub2', 'test2.obj'], "myfortran.py wrote test2.obj\n") test.must_match(['sub2', 'mod_foo.mod'], "myfortran.py wrote mod_foo.mod\n") @@ -95,8 +103,6 @@ test.must_match(['sub3', 'mod_foo.mod'], "myfortran.py wrote mod_foo.mod\n") test.up_to_date(arguments = '.') - - test.pass_test() # Local Variables: -- cgit v0.12 From 1d7a78c786aa3bd9ae45d34773b10be949dad25a Mon Sep 17 00:00:00 2001 From: maiphi Date: Sun, 27 May 2018 18:19:03 +0200 Subject: Test FORTRANMODDIR.py: use F90 everywhere. --- test/Fortran/FORTRANMODDIR.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/Fortran/FORTRANMODDIR.py b/test/Fortran/FORTRANMODDIR.py index e6f2413..61dcc45 100644 --- a/test/Fortran/FORTRANMODDIR.py +++ b/test/Fortran/FORTRANMODDIR.py @@ -31,7 +31,7 @@ Verify the following things: * The dependency scanner does not expect a module file to be created from a "module procedure" statement. * The dependency scanner expects the module files to be created in the correct - module directory (is is verified by the test.up_to_date()). + module directory (this is verified by the test.up_to_date()). """ import TestSCons @@ -58,17 +58,17 @@ for t in sys.argv[3:] + modules: """) test.write('SConstruct', """ -env = Environment(FORTRANCOM = r'%(_python_)s myfortran.py $_FORTRANMODFLAG $SOURCE $TARGET', +env = Environment(F90COM = r'%(_python_)s myfortran.py $_FORTRANMODFLAG $SOURCE $TARGET', FORTRANMODDIRPREFIX='moduledir=', FORTRANMODDIR='modules') -env.Object(target = 'test1.obj', source = 'test1.f') -env.Object(target = 'sub2/test2.obj', source = 'test1.f', +env.Object(target = 'test1.obj', source = 'test1.f90') +env.Object(target = 'sub2/test2.obj', source = 'test1.f90', FORTRANMODDIR='${TARGET.dir}') -env.Object(target = 'sub3/test3.obj', source = 'test1.f', - FORTRANCOM = r'%(_python_)s myfortran.py moduledir=$FORTRANMODDIR $SOURCE $TARGET', +env.Object(target = 'sub3/test3.obj', source = 'test1.f90', + F90COM = r'%(_python_)s myfortran.py moduledir=$FORTRANMODDIR $SOURCE $TARGET', FORTRANMODDIR='${TARGET.dir}') """ % locals()) -test.write('test1.f', """\ +test.write('test1.f90', """\ module mod_foo implicit none interface q @@ -83,7 +83,7 @@ end module mod_foo program test use mod_foo implicit none - print *, 'test.f' + print *, 'test1.f90' call p call q end -- cgit v0.12 From b649dec7073d1fb1e9fd08b57f2a95288eba6965 Mon Sep 17 00:00:00 2001 From: grbd Date: Mon, 28 May 2018 02:39:14 +0100 Subject: set the pip installation directory to be non version specific --- src/setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/setup.py b/src/setup.py index 442315c..273da18 100644 --- a/src/setup.py +++ b/src/setup.py @@ -247,10 +247,12 @@ class install_lib(_install_lib): if Options.standalone_lib: # ...but they asked for a standalone directory. self.install_dir = os.path.join(prefix, "scons") - elif Options.version_lib or not Options.standard_lib: + elif Options.version_lib: # ...they asked for a version-specific directory, - # or they get it by default. self.install_dir = os.path.join(prefix, "scons-%s" % Version) + elif not Options.standard_lib: + # default. + self.install_dir = os.path.join(prefix, "scons") msg = "Installed SCons library modules into %s" % self.install_dir Installed.append(msg) -- cgit v0.12 From 1e4effee750f8a1bec2f982b1a46b9f4ab14cf04 Mon Sep 17 00:00:00 2001 From: grbd Date: Mon, 28 May 2018 02:45:35 +0100 Subject: updates Changes.txt --- src/CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 0544f60..f74565a 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -15,6 +15,8 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From Richard West: - Add SConstruct.py, Sconstruct.py, sconstruct.py to the search path for the root SConstruct file. Allows easier debugging within Visual Studio + - Set the default pip installation directory to be non version specific + to aid with development of external tools (such as using pytest) From Bernard Blackham: - Fixed handling of side-effects in task master (fixes #3013). -- cgit v0.12 From 370e4f79cc9da517926270b8bda352b14e43cbf0 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Tue, 29 May 2018 16:53:56 -0700 Subject: Reworded description of scons setup.py changes. --- src/CHANGES.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index f74565a..b0baecb 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -15,8 +15,9 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From Richard West: - Add SConstruct.py, Sconstruct.py, sconstruct.py to the search path for the root SConstruct file. Allows easier debugging within Visual Studio - - Set the default pip installation directory to be non version specific - to aid with development of external tools (such as using pytest) + - Change setup.py to change the install directory (via pip, or setup.py install) from scons-#.#.# + to scons (Yielding /lib/scons/SCons/ instead of /lib/scons/SCons-#.#.#/). + This changes SCons to better comply with normal Python installation practices. From Bernard Blackham: - Fixed handling of side-effects in task master (fixes #3013). -- cgit v0.12 From e5d006c8ee3b6e3b364153828db15fb0ed3f776b Mon Sep 17 00:00:00 2001 From: Isaac Pascual Monells Date: Tue, 19 Jun 2018 11:16:35 +0200 Subject: Fix documentation apostrophe --- src/engine/SCons/Environment.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/SCons/Environment.xml b/src/engine/SCons/Environment.xml index ccee68d..324096f 100644 --- a/src/engine/SCons/Environment.xml +++ b/src/engine/SCons/Environment.xml @@ -60,7 +60,7 @@ or this: env = Environment() -env['BUILDERS]['NewBuilder'] = foo +env['BUILDERS']['NewBuilder'] = foo -- cgit v0.12 From 783a977936a6573ecc71dbbbe23116a2c94fcc51 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 22 Jun 2018 17:04:03 -0400 Subject: Fix bug where constant string in python action yielded trying to join an array of items one of which was unicode. --- src/engine/SCons/Util.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index ec29796..b566088 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -1610,9 +1610,13 @@ del __revision__ def to_bytes (s): if s is None: return b'None' + if not PY3 and isinstance(s, UnicodeType): + # PY2, must encode unicode + return bytes(s, 'utf-8') if isinstance (s, (bytes, bytearray)) or bytes is str: + # Above case not covered here as py2 bytes and strings are the same return s - return bytes (s, 'utf-8') + return bytes(s, 'utf-8') def to_str (s): if s is None: -- cgit v0.12 From 6856b04629a18e9147ee6838f630b0367c56e8d8 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 22 Jun 2018 17:25:38 -0400 Subject: Add test for py2 to_Bytes properly handling unicode string and converting it to a bytearray utf-8 encoded. This was breaking python action functions with unicode constant strings --- src/engine/SCons/UtilTests.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 6b12f8a..38b4da9 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -307,6 +307,14 @@ class UtilTestCase(unittest.TestCase): if HasUnicode: exec("assert not is_Tuple(u'')") + def test_to_Bytes(self): + """ Test the to_Bytes method""" + if not PY3: + self.assertEqual(to_bytes(UnicodeType('Hello')), + bytearray(u'Hello', 'utf-8'), + "Check that to_bytes creates byte array when presented with unicode string. PY2 only") + + def test_to_String(self): """Test the to_String() method.""" assert to_String(1) == "1", to_String(1) @@ -843,17 +851,8 @@ class flattenTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ dictifyTestCase, - flattenTestCase, - MD5TestCase, - NodeListTestCase, - UtilTestCase, - ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() + # Local Variables: # tab-width:4 -- cgit v0.12 From 97f9431309596152d9cb093172e9752d2b744303 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 22 Jun 2018 17:26:07 -0400 Subject: Create bytearray and not bytes as no bytes type for py2 --- src/engine/SCons/Util.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index b566088..52f42d2 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -1607,18 +1607,20 @@ class NullSeq(Null): del __revision__ -def to_bytes (s): + +def to_bytes(s): if s is None: return b'None' if not PY3 and isinstance(s, UnicodeType): # PY2, must encode unicode - return bytes(s, 'utf-8') + return bytearray(s, 'utf-8') if isinstance (s, (bytes, bytearray)) or bytes is str: # Above case not covered here as py2 bytes and strings are the same return s return bytes(s, 'utf-8') -def to_str (s): + +def to_str(s): if s is None: return 'None' if bytes is str or is_String(s): @@ -1626,8 +1628,6 @@ def to_str (s): return str (s, 'utf-8') - -# No cmp in py3, so we'll define it. def cmp(a, b): """ Define cmp because it's no longer available in python3 -- cgit v0.12 From 256000d738fcbf4de12e9d76d4ca6b6317e70a33 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 22 Jun 2018 17:29:10 -0400 Subject: PEP8 plus remove testsuite logic and switch to simpler unittest.main(). This allows easily running a single test --- src/engine/SCons/ActionTests.py | 523 +++++++++++++++++++++++++--------------- 1 file changed, 327 insertions(+), 196 deletions(-) diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 2398c10..2959ede 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -23,6 +23,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + # Define a null function and a null class for use as builder actions. # Where these are defined in the file seems to affect their byte-code # contents, so try to minimize changes by defining them here, before we @@ -30,10 +31,12 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" def GlobalFunc(): pass + class GlobalActFunc(object): def __call__(self): pass + import collections import io import os @@ -56,7 +59,7 @@ import TestUnit # We don't do this as a setUp() method because it's # unnecessary to create a separate directory and script # for each test, they can just use the one. -test = TestCmd.TestCmd(workdir = '') +test = TestCmd.TestCmd(workdir='') test.write('act.py', """\ import os, string, sys @@ -100,6 +103,7 @@ scons_env = SCons.Environment.Environment() # so it doesn't clutter the output. sys.stdout = io.StringIO() + class CmdStringHolder(object): def __init__(self, cmd, literal=None): self.data = str(cmd) @@ -125,6 +129,7 @@ class CmdStringHolder(object): else: return self.data + class Environment(object): def __init__(self, **kw): self.d = {} @@ -134,53 +139,70 @@ class Environment(object): self.d['ESCAPE'] = scons_env['ESCAPE'] for k, v in kw.items(): self.d[k] = v + # Just use the underlying scons_subst*() utility methods. def subst(self, strSubst, raw=0, target=[], source=[], conv=None): return SCons.Subst.scons_subst(strSubst, self, raw, target, source, self.d, conv=conv) + subst_target_source = subst + def subst_list(self, strSubst, raw=0, target=[], source=[], conv=None): return SCons.Subst.scons_subst_list(strSubst, self, raw, - target, source, self.d, conv=conv) + target, source, self.d, conv=conv) + def __getitem__(self, item): return self.d[item] + def __setitem__(self, item, value): self.d[item] = value + def has_key(self, item): return item in self.d + def get(self, key, value=None): return self.d.get(key, value) + def items(self): return list(self.d.items()) + def Dictionary(self): return self.d + def Clone(self, **kw): res = Environment() res.d = SCons.Util.semi_deepcopy(self.d) for k, v in kw.items(): res.d[k] = v return res + def sig_dict(self): d = {} - for k,v in self.items(): d[k] = v + for k, v in self.items(): d[k] = v d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__'] d['TARGET'] = d['TARGETS'][0] d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__'] d['SOURCE'] = d['SOURCES'][0] return d + class DummyNode(object): def __init__(self, name): self.name = name + def str_for_display(self): return '"' + self.name + '"' + def __str__(self): return self.name + def rfile(self): return self + def get_subst_proxy(self): return self + if os.name == 'java': python = os.path.join(sys.prefix, 'jython') else: @@ -189,6 +211,7 @@ _python_ = test.escape(python) _null = SCons.Action._null + def test_varlist(pos_call, str_call, cmd, cmdstrfunc, **kw): def call_action(a, pos_call=pos_call, str_call=str_call, kw=kw): a = SCons.Action.Action(*a, **kw) @@ -226,6 +249,7 @@ def test_varlist(pos_call, str_call, cmd, cmdstrfunc, **kw): a = call_action((cmd, cmdstrfunc, 'a', 'b', 'c')) assert a.varlist == ('a', 'b', 'c', 'x', 'y', 'z'), a.varlist + def test_positional_args(pos_callback, cmd, **kw): """Test that Action() returns the expected type and that positional args work. """ @@ -235,7 +259,9 @@ def test_positional_args(pos_callback, cmd, **kw): if not isinstance(act, SCons.Action._ActionAction): # only valid cmdstrfunc is None - def none(a): pass + def none(a): + pass + test_varlist(pos_callback, none, cmd, None, **kw) else: # _ActionAction should have set these @@ -244,22 +270,27 @@ def test_positional_args(pos_callback, cmd, **kw): assert act.presub is _null, act.presub assert act.chdir is None, act.chdir assert act.exitstatfunc is SCons.Action.default_exitstatfunc, \ - act.exitstatfunc + act.exitstatfunc def cmdstr(a): assert hasattr(a, 'strfunction') assert a.cmdstr == 'cmdstr', a.cmdstr + test_varlist(pos_callback, cmdstr, cmd, 'cmdstr', **kw) - def fun(): pass + def fun(): + pass + def strfun(a, fun=fun): assert a.strfunction is fun, a.strfunction assert a.cmdstr == _null, a.cmdstr + test_varlist(pos_callback, strfun, cmd, fun, **kw) def none(a): assert hasattr(a, 'strfunction') assert a.cmdstr is None, a.cmdstr + test_varlist(pos_callback, none, cmd, None, **kw) """Test handling of bad cmdstrfunc arguments """ @@ -274,18 +305,21 @@ def test_positional_args(pos_callback, cmd, **kw): return act + class ActionTestCase(unittest.TestCase): """Test the Action() factory function""" def test_FunctionAction(self): """Test the Action() factory's creation of FunctionAction objects """ + def foo(): pass def func_action(a, foo=foo): assert isinstance(a, SCons.Action.FunctionAction), a assert a.execfunction == foo, a.execfunction + test_positional_args(func_action, foo) # a singleton list returns the contained action test_positional_args(func_action, [foo]) @@ -293,28 +327,33 @@ class ActionTestCase(unittest.TestCase): def test_CommandAction(self): """Test the Action() factory's creation of CommandAction objects """ + def cmd_action(a): assert isinstance(a, SCons.Action.CommandAction), a assert a.cmd_list == "string", a.cmd_list + test_positional_args(cmd_action, "string") # a singleton list returns the contained action test_positional_args(cmd_action, ["string"]) - try: unicode - except NameError: pass + try: + unicode + except NameError: + pass else: a2 = eval("SCons.Action.Action(u'string')") assert isinstance(a2, SCons.Action.CommandAction), a2 def line_action(a): assert isinstance(a, SCons.Action.CommandAction), a - assert a.cmd_list == [ "explicit", "command", "line" ], a.cmd_list - test_positional_args(line_action, [[ "explicit", "command", "line" ]]) + assert a.cmd_list == ["explicit", "command", "line"], a.cmd_list + + test_positional_args(line_action, [["explicit", "command", "line"]]) def test_ListAction(self): """Test the Action() factory's creation of ListAction objects """ - a1 = SCons.Action.Action(["x", "y", "z", [ "a", "b", "c"]]) + a1 = SCons.Action.Action(["x", "y", "z", ["a", "b", "c"]]) assert isinstance(a1, SCons.Action.ListAction), a1 assert a1.varlist == (), a1.varlist assert isinstance(a1.list[0], SCons.Action.CommandAction), a1.list[0] @@ -324,7 +363,7 @@ class ActionTestCase(unittest.TestCase): assert isinstance(a1.list[2], SCons.Action.CommandAction), a1.list[2] assert a1.list[2].cmd_list == "z", a1.list[2].cmd_list assert isinstance(a1.list[3], SCons.Action.CommandAction), a1.list[3] - assert a1.list[3].cmd_list == [ "a", "b", "c" ], a1.list[3].cmd_list + assert a1.list[3].cmd_list == ["a", "b", "c"], a1.list[3].cmd_list a2 = SCons.Action.Action("x\ny\nz") assert isinstance(a2, SCons.Action.ListAction), a2 @@ -372,20 +411,24 @@ class ActionTestCase(unittest.TestCase): def test_CommandGeneratorAction(self): """Test the Action() factory's creation of CommandGeneratorAction objects """ + def foo(): pass def gen_action(a, foo=foo): assert isinstance(a, SCons.Action.CommandGeneratorAction), a assert a.generator is foo, a.generator + test_positional_args(gen_action, foo, generator=1) def test_LazyCmdGeneratorAction(self): """Test the Action() factory's creation of lazy CommandGeneratorAction objects """ + def lazy_action(a): assert isinstance(a, SCons.Action.LazyAction), a assert a.var == "FOO", a.var assert a.cmd_list == "${FOO}", a.cmd_list + test_positional_args(lazy_action, "$FOO") test_positional_args(lazy_action, "${FOO}") @@ -406,6 +449,7 @@ class ActionTestCase(unittest.TestCase): a2 = SCons.Action.Action(a1) assert a2 is a1, a2 + class _ActionActionTestCase(unittest.TestCase): def test__init__(self): @@ -429,7 +473,7 @@ class _ActionActionTestCase(unittest.TestCase): assert a.chdir is None, a.chdir assert a.exitstatfunc is SCons.Action.default_exitstatfunc, a.exitstatfunc - assert SCons.Action._ActionAction(kwarg = 1) + assert SCons.Action._ActionAction(kwarg=1) assert not hasattr(a, 'kwarg') assert not hasattr(a, 'strfunction') assert a.cmdstr is _null, a.cmdstr @@ -453,7 +497,7 @@ class _ActionActionTestCase(unittest.TestCase): assert not hasattr(a, 'strfunction') assert a.cmdstr is None, a.cmdstr - t = ('a','b','c') + t = ('a', 'b', 'c') a = SCons.Action._ActionAction(varlist=t) assert a.varlist == t, a.varlist @@ -467,14 +511,14 @@ class _ActionActionTestCase(unittest.TestCase): assert a.exitstatfunc is func1, a.exitstatfunc a = SCons.Action._ActionAction( - # alphabetical order ... - chdir='x', - cmdstr='cmdstr', - exitstatfunc=func3, - presub=func2, - strfunction=func1, - varlist=t, - ) + # alphabetical order ... + chdir='x', + cmdstr='cmdstr', + exitstatfunc=func3, + presub=func2, + strfunction=func1, + varlist=t, + ) assert a.chdir is 'x', a.chdir assert a.cmdstr is 'cmdstr', a.cmdstr assert a.exitstatfunc is func3, a.exitstatfunc @@ -485,7 +529,10 @@ class _ActionActionTestCase(unittest.TestCase): def test_dup_keywords(self): """Test handling of both cmdstr and strfunction arguments """ - def func(): pass + + def func(): + pass + try: a = SCons.Action.Action('foo', cmdstr='string', strfunction=func) except SCons.Errors.UserError as e: @@ -513,6 +560,7 @@ class _ActionActionTestCase(unittest.TestCase): try: def execfunc(target, source, env): pass + a = SCons.Action.Action(execfunc) sio = io.StringIO() @@ -532,9 +580,9 @@ class _ActionActionTestCase(unittest.TestCase): save_print_actions = SCons.Action.print_actions save_print_actions_presub = SCons.Action.print_actions_presub save_execute_actions = SCons.Action.execute_actions - #SCons.Action.print_actions = 0 + # SCons.Action.print_actions = 0 - test = TestCmd.TestCmd(workdir = '') + test = TestCmd.TestCmd(workdir='') test.subdir('sub', 'xyz') os.chdir(test.workpath()) @@ -545,16 +593,19 @@ class _ActionActionTestCase(unittest.TestCase): assert isinstance(target, list), type(target) assert isinstance(source, list), type(source) return 7 + a = SCons.Action.Action(execfunc) def firstfunc(target, source, env): assert isinstance(target, list), type(target) assert isinstance(source, list), type(source) return 0 + def lastfunc(target, source, env): assert isinstance(target, list), type(target) assert isinstance(source, list), type(source) return 9 + b = SCons.Action.Action([firstfunc, execfunc, lastfunc]) sio = io.StringIO() @@ -694,8 +745,10 @@ class _ActionActionTestCase(unittest.TestCase): SCons.Action.execute_actions = 1 result = [] + def my_print_cmd_line(s, target, source, env, result=result): result.append(s) + env['PRINT_CMD_LINE_FUNC'] = my_print_cmd_line a("output", "input", env) assert result == ["execfunc(['output'], ['input'])"], result @@ -721,39 +774,43 @@ class _ActionActionTestCase(unittest.TestCase): def func(): pass + a = SCons.Action.Action(func) s = a.presub_lines(env) assert s == ["func(target, source, env)"], s def gen(target, source, env, for_signature): return 'generat' + env.get('GEN', 'or') + a = SCons.Action.Action(gen, generator=1) s = a.presub_lines(env) assert s == ["generator"], s - s = a.presub_lines(Environment(GEN = 'ed')) + s = a.presub_lines(Environment(GEN='ed')) assert s == ["generated"], s a = SCons.Action.Action("$ACT") s = a.presub_lines(env) assert s == [''], s - s = a.presub_lines(Environment(ACT = 'expanded action')) + s = a.presub_lines(Environment(ACT='expanded action')) assert s == ['expanded action'], s def test_add(self): """Test adding Actions to stuff.""" + # Adding actions to other Actions or to stuff that can # be converted into an Action should produce a ListAction # containing all the Actions. def bar(): return None + baz = SCons.Action.Action(bar, generator=1) act1 = SCons.Action.Action('foo bar') - act2 = SCons.Action.Action([ 'foo', bar ]) + act2 = SCons.Action.Action(['foo', bar]) sum = act1 + act2 assert isinstance(sum, SCons.Action.ListAction), str(sum) assert len(sum.list) == 3, len(sum.list) - assert [isinstance(x, SCons.Action.ActionBase) for x in sum.list] == [ 1, 1, 1 ] + assert [isinstance(x, SCons.Action.ActionBase) for x in sum.list] == [1, 1, 1] sum = act1 + act1 assert isinstance(sum, SCons.Action.ListAction), str(sum) @@ -789,13 +846,13 @@ class _ActionActionTestCase(unittest.TestCase): assert len(sum.list) == 3, len(sum.list) assert isinstance(sum.list[0], SCons.Action.CommandAction) - sum = [ 'foo', 'bar' ] + act1 + sum = ['foo', 'bar'] + act1 assert isinstance(sum, SCons.Action.ListAction), str(sum) assert len(sum.list) == 3, sum.list assert isinstance(sum.list[0], SCons.Action.CommandAction) assert isinstance(sum.list[1], SCons.Action.CommandAction) - sum = act2 + [ baz, bar ] + sum = act2 + [baz, bar] assert isinstance(sum, SCons.Action.ListAction), str(sum) assert len(sum.list) == 4, len(sum.list) assert isinstance(sum.list[2], SCons.Action.CommandGeneratorAction) @@ -822,17 +879,18 @@ class _ActionActionTestCase(unittest.TestCase): else: assert 0, "Should have thrown a TypeError adding to an int." + class CommandActionTestCase(unittest.TestCase): def test___init__(self): """Test creation of a command Action """ a = SCons.Action.CommandAction(["xyzzy"]) - assert a.cmd_list == [ "xyzzy" ], a.cmd_list + assert a.cmd_list == ["xyzzy"], a.cmd_list assert a.cmdstr is _null, a.cmdstr a = SCons.Action.CommandAction(["abra"], cmdstr="cadabra") - assert a.cmd_list == [ "abra" ], a.cmd_list + assert a.cmd_list == ["abra"], a.cmd_list assert a.cmdstr == "cadabra", a.cmdstr def test___str__(self): @@ -952,6 +1010,7 @@ class CommandActionTestCase(unittest.TestCase): def sf(target, source, env): return "sf was called" + act = SCons.Action.CommandAction('foo', strfunction=sf) s = act.strfunction([], [], env) assert s == "sf was called", s @@ -959,26 +1018,35 @@ class CommandActionTestCase(unittest.TestCase): class actclass1(object): def __init__(self, targets, sources, env): pass + def __call__(self): return 1 + class actclass2(object): def __init__(self, targets, sources, env): self.strfunction = 5 + def __call__(self): return 2 + class actclass3(object): def __init__(self, targets, sources, env): pass + def __call__(self): return 3 + def strfunction(self, targets, sources, env): - return 'actclass3 on %s to get %s'%(str(sources[0]), - str(targets[0])) + return 'actclass3 on %s to get %s' % (str(sources[0]), + str(targets[0])) + class actclass4(object): def __init__(self, targets, sources, env): pass + def __call__(self): return 4 + strfunction = None act1 = SCons.Action.Action(actclass1([t1], [s1], env)) @@ -1075,7 +1143,7 @@ class CommandActionTestCase(unittest.TestCase): act = SCons.Action.CommandAction(cmd4) sources = [DummyNode('three'), DummyNode('four'), DummyNode('five')] env2 = env.Clone() - r = act([], source = sources, env = env2) + r = act([], source=sources, env=env2) assert r == 0 c = test.read(outfile, 'r') assert c == "act.py: 'three' 'four'\n", c @@ -1092,13 +1160,13 @@ class CommandActionTestCase(unittest.TestCase): PATH = '' env5['ENV']['XYZZY'] = 'xyzzy' - r = act(target = DummyNode('out5'), source = [], env = env5) + r = act(target=DummyNode('out5'), source=[], env=env5) act = SCons.Action.CommandAction(cmd5) - r = act(target = DummyNode('out5'), - source = [], - env = env.Clone(ENV = {'XYZZY' : 'xyzzy5', - 'PATH' : PATH})) + r = act(target=DummyNode('out5'), + source=[], + env=env.Clone(ENV={'XYZZY': 'xyzzy5', + 'PATH': PATH})) assert r == 0 c = test.read(outfile, 'r') assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy5'\n", c @@ -1106,19 +1174,22 @@ class CommandActionTestCase(unittest.TestCase): class Obj(object): def __init__(self, str): self._str = str + def __str__(self): return self._str + def rfile(self): return self + def get_subst_proxy(self): return self cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (_python_, act_py, outfile) act = SCons.Action.CommandAction(cmd6) - r = act(target = [Obj('111'), Obj('222')], - source = [Obj('333'), Obj('444'), Obj('555')], - env = env.Clone()) + r = act(target=[Obj('111'), Obj('222')], + source=[Obj('333'), Obj('444'), Obj('555')], + env=env.Clone()) assert r == 0 c = test.read(outfile, 'r') assert c == "act.py: '222' '111' '333' '444'\n", c @@ -1133,7 +1204,7 @@ class CommandActionTestCase(unittest.TestCase): expect_nonexistent = 127 # Newer cygwin seems to return 126 for following expect_nonexecutable_file = 126 - expect_nonexecutable_dir = 127 + expect_nonexecutable_dir = 127 elif sys.platform.find('sunos') != -1: expect_nonexistent = 1 expect_nonexecutable_file = 1 @@ -1141,22 +1212,22 @@ class CommandActionTestCase(unittest.TestCase): else: expect_nonexistent = 127 expect_nonexecutable_file = 126 - expect_nonexecutable_dir = 126 + expect_nonexecutable_dir = 126 # Test that a nonexistent command returns 127 act = SCons.Action.CommandAction(python + "_no_such_command_") - r = act([], [], env.Clone(out = outfile)) + r = act([], [], env.Clone(out=outfile)) assert r.status == expect_nonexistent, r.status # Test that trying to execute a directory returns 126 dir, tail = os.path.split(python) act = SCons.Action.CommandAction(dir) - r = act([], [], env.Clone(out = outfile)) + r = act([], [], env.Clone(out=outfile)) assert r.status == expect_nonexecutable_file, r.status # Test that trying to execute a non-executable file returns 126 act = SCons.Action.CommandAction(outfile) - r = act([], [], env.Clone(out = outfile)) + r = act([], [], env.Clone(out=outfile)) assert r.status == expect_nonexecutable_dir, r.status act = SCons.Action.CommandAction('%s %s 1' % (_python_, exit_py)) @@ -1190,51 +1261,59 @@ class CommandActionTestCase(unittest.TestCase): def test_set_handler(self): """Test setting the command handler... """ + class Test(object): def __init__(self): self.executed = 0 - t=Test() + + t = Test() + def func(sh, escape, cmd, args, env, test=t): test.executed = args test.shell = sh return 0 + def escape_func(cmd): return '**' + cmd + '**' class LiteralStr(object): def __init__(self, x): self.data = x + def __str__(self): return self.data + def escape(self, escape_func): return escape_func(self.data) + def is_literal(self): return 1 a = SCons.Action.CommandAction(["xyzzy"]) - e = Environment(SPAWN = func) + e = Environment(SPAWN=func) a([], [], e) - assert t.executed == [ 'xyzzy' ], t.executed + assert t.executed == ['xyzzy'], t.executed a = SCons.Action.CommandAction(["xyzzy"]) - e = Environment(SPAWN = '$FUNC', FUNC = func) + e = Environment(SPAWN='$FUNC', FUNC=func) a([], [], e) - assert t.executed == [ 'xyzzy' ], t.executed + assert t.executed == ['xyzzy'], t.executed a = SCons.Action.CommandAction(["xyzzy"]) - e = Environment(SPAWN = func, SHELL = 'fake shell') + e = Environment(SPAWN=func, SHELL='fake shell') a([], [], e) - assert t.executed == [ 'xyzzy' ], t.executed + assert t.executed == ['xyzzy'], t.executed assert t.shell == 'fake shell', t.shell - a = SCons.Action.CommandAction([ LiteralStr("xyzzy") ]) - e = Environment(SPAWN = func, ESCAPE = escape_func) + a = SCons.Action.CommandAction([LiteralStr("xyzzy")]) + e = Environment(SPAWN=func, ESCAPE=escape_func) a([], [], e) - assert t.executed == [ '**xyzzy**' ], t.executed + assert t.executed == ['**xyzzy**'], t.executed def test_get_contents(self): """Test fetching the contents of a command Action """ + def CmdGen(target, source, env, for_signature): assert for_signature return "%s %s" % \ @@ -1242,10 +1321,10 @@ class CommandActionTestCase(unittest.TestCase): # The number 1 is there to make sure all args get converted to strings. a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$(", "$bar", - "$)", "stuff", "$)", "|", "$baz", 1]) + "$)", "stuff", "$)", "|", "$baz", 1]) c = a.get_contents(target=[], source=[], - env=Environment(foo = 'FFF', bar = 'BBB', - baz = CmdGen)) + env=Environment(foo='FFF', bar='BBB', + baz=CmdGen)) assert c == b"| | FFF BBB 1", c # Make sure that CommandActions use an Environment's @@ -1254,9 +1333,9 @@ class CommandActionTestCase(unittest.TestCase): def subst_target_source(self, strSubst, raw=0, target=[], source=[]): return 'subst_target_source: ' + strSubst - c = a.get_contents(target=DummyNode('ttt'), source = DummyNode('sss'), - env=SpecialEnvironment(foo = 'GGG', bar = 'CCC', - baz = 'ZZZ')) + c = a.get_contents(target=DummyNode('ttt'), source=DummyNode('sss'), + env=SpecialEnvironment(foo='GGG', bar='CCC', + baz='ZZZ')) assert c == b'subst_target_source: | $( $foo | $( $bar $) stuff $) | $baz 1', c # We've discussed using the real target and source names in a @@ -1304,6 +1383,7 @@ class CommandActionTestCase(unittest.TestCase): c = a.get_contents(target=t, source=s, env=env) assert c == b"s4 s5", c + class CommandGeneratorActionTestCase(unittest.TestCase): def factory(self, act, **kw): @@ -1313,16 +1393,18 @@ class CommandGeneratorActionTestCase(unittest.TestCase): def test___init__(self): """Test creation of a command generator Action """ + def f(target, source, env): pass + a = self.factory(f) assert a.generator == f def test___str__(self): """Test the pre-substitution strings for command generator Actions """ - def f(target, source, env, for_signature, self=self): + def f(target, source, env, for_signature, self=self): # See if "env" is really a construction environment (or # looks like one) by accessing the FindIxes attribute. # (The Tool/mingw.py module has a generator that uses this, @@ -1331,6 +1413,7 @@ class CommandGeneratorActionTestCase(unittest.TestCase): env.FindIxes return "FOO" + a = self.factory(f) s = str(a) assert s == 'FOO', s @@ -1338,10 +1421,12 @@ class CommandGeneratorActionTestCase(unittest.TestCase): def test_genstring(self): """Test the command generator Action genstring() method """ + def f(target, source, env, for_signature, self=self): dummy = env['dummy'] self.dummy = dummy return "$FOO $TARGET $SOURCE $TARGETS $SOURCES" + a = self.factory(f) self.dummy = 0 s = a.genstring([], [], env=Environment(FOO='xyzzy', dummy=1)) @@ -1358,13 +1443,16 @@ class CommandGeneratorActionTestCase(unittest.TestCase): s = env.subst("$FOO") assert s == 'foo baz\nbar ack', s return "$FOO" + def func_action(target, source, env, self=self): - dummy=env['dummy'] + dummy = env['dummy'] s = env.subst('$foo') assert s == 'bar', s - self.dummy=dummy + self.dummy = dummy + def f2(target, source, env, for_signature, f=func_action): return f + def ch(sh, escape, cmd, args, env, self=self): self.cmd.append(cmd) self.args.append(args) @@ -1373,30 +1461,34 @@ class CommandGeneratorActionTestCase(unittest.TestCase): self.dummy = 0 self.cmd = [] self.args = [] - a([], [], env=Environment(FOO = 'foo baz\nbar ack', - dummy = 1, - SPAWN = ch)) + a([], [], env=Environment(FOO='foo baz\nbar ack', + dummy=1, + SPAWN=ch)) assert self.dummy == 1, self.dummy assert self.cmd == ['foo', 'bar'], self.cmd - assert self.args == [[ 'foo', 'baz' ], [ 'bar', 'ack' ]], self.args + assert self.args == [['foo', 'baz'], ['bar', 'ack']], self.args b = self.factory(f2) self.dummy = 0 - b(target=[], source=[], env=Environment(foo = 'bar', - dummy = 2 )) - assert self.dummy==2, self.dummy + b(target=[], source=[], env=Environment(foo='bar', + dummy=2)) + assert self.dummy == 2, self.dummy del self.dummy class DummyFile(object): def __init__(self, t): self.t = t + def rfile(self): self.t.rfile_called = 1 return self + def get_subst_proxy(self): return self + def f3(target, source, env, for_signature): return '' + c = self.factory(f3) c(target=[], source=DummyFile(self), env=Environment()) assert self.rfile_called @@ -1404,19 +1496,20 @@ class CommandGeneratorActionTestCase(unittest.TestCase): def test_get_contents(self): """Test fetching the contents of a command generator Action """ + def f(target, source, env, for_signature): foo = env['foo'] bar = env['bar'] assert for_signature, for_signature return [["guux", foo, "$(", "$ignore", "$)", bar, - '${test("$( foo $bar $)")}' ]] + '${test("$( foo $bar $)")}']] def test(mystr): assert mystr == "$( foo $bar $)", mystr return "test" - env = Environment(foo = 'FFF', bar = 'BBB', - ignore = 'foo', test=test) + env = Environment(foo='FFF', bar='BBB', + ignore='foo', test=test) a = self.factory(f) c = a.get_contents(target=[], source=[], env=env) assert c == b"guux FFF BBB test", c @@ -1430,9 +1523,9 @@ class CommandGeneratorActionTestCase(unittest.TestCase): # Since the python bytecode has per version differences, we need different expected results per version func_matches = { - (2,7) : bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), - (3,5) : bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), - (3,6) : bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), + (2, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), + (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), + (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), } meth_matches = [ @@ -1446,15 +1539,17 @@ class CommandGeneratorActionTestCase(unittest.TestCase): def f_local(target, source, env, for_signature): return SCons.Action.Action(LocalFunc) - env = Environment(XYZ = 'foo') + env = Environment(XYZ='foo') a = self.factory(f_global) c = a.get_contents(target=[], source=[], env=env) - assert c == func_matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected \n"+repr(func_matches[sys.version_info[:2]]) + assert c == func_matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected \n" + repr( + func_matches[sys.version_info[:2]]) a = self.factory(f_local) c = a.get_contents(target=[], source=[], env=env) - assert c == func_matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected \n"+repr(func_matches[sys.version_info[:2]]) + assert c == func_matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected \n" + repr( + func_matches[sys.version_info[:2]]) def f_global(target, source, env, for_signature): return SCons.Action.Action(GlobalFunc, varlist=['XYZ']) @@ -1478,12 +1573,16 @@ class FunctionActionTestCase(unittest.TestCase): def test___init__(self): """Test creation of a function Action """ + def func1(): pass + def func2(): pass + def func3(): pass + def func4(): pass @@ -1491,15 +1590,17 @@ class FunctionActionTestCase(unittest.TestCase): assert a.execfunction == func1, a.execfunction assert isinstance(a.strfunction, types.MethodType), type(a.strfunction) - a = SCons.Action.FunctionAction(func2, { 'strfunction' : func3 }) + a = SCons.Action.FunctionAction(func2, {'strfunction': func3}) assert a.execfunction == func2, a.execfunction assert a.strfunction == func3, a.strfunction def test___str__(self): """Test the __str__() method for function Actions """ + def func1(): pass + a = SCons.Action.FunctionAction(func1, {}) s = str(a) assert s == "func1(target, source, env)", s @@ -1507,6 +1608,7 @@ class FunctionActionTestCase(unittest.TestCase): class class1(object): def __call__(self): pass + a = SCons.Action.FunctionAction(class1(), {}) s = str(a) assert s == "class1(target, source, env)", s @@ -1515,22 +1617,25 @@ class FunctionActionTestCase(unittest.TestCase): """Test executing a function Action """ self.inc = 0 + def f(target, source, env): s = env['s'] s.inc = s.inc + 1 s.target = target - s.source=source + s.source = source assert env.subst("$BAR") == 'foo bar', env.subst("$BAR") return 0 + a = SCons.Action.FunctionAction(f, {}) - a(target=1, source=2, env=Environment(BAR = 'foo bar', - s = self)) + a(target=1, source=2, env=Environment(BAR='foo bar', + s=self)) assert self.inc == 1, self.inc assert self.source == [2], self.source assert self.target == [1], self.target global count count = 0 + def function1(target, source, env): global count count = count + 1 @@ -1540,7 +1645,7 @@ class FunctionActionTestCase(unittest.TestCase): return 1 act = SCons.Action.FunctionAction(function1, {}) - r = act(target = [outfile, outfile2], source=[], env=Environment()) + r = act(target=[outfile, outfile2], source=[], env=Environment()) assert r.status == 1, r.status assert count == 1, count @@ -1555,7 +1660,7 @@ class FunctionActionTestCase(unittest.TestCase): f.write("class1a\n") act = SCons.Action.FunctionAction(class1a, {}) - r = act([], [], Environment(out = outfile)) + r = act([], [], Environment(out=outfile)) assert isinstance(r.status, class1a), r.status c = test.read(outfile, 'r') assert c == "class1a\n", c @@ -1567,7 +1672,7 @@ class FunctionActionTestCase(unittest.TestCase): return 2 act = SCons.Action.FunctionAction(class1b(), {}) - r = act([], [], Environment(out = outfile)) + r = act([], [], Environment(out=outfile)) assert r.status == 2, r.status c = test.read(outfile, 'r') assert c == "class1b\n", c @@ -1575,11 +1680,13 @@ class FunctionActionTestCase(unittest.TestCase): def build_it(target, source, env, executor=None, self=self): self.build_it = 1 return 0 + def string_it(target, source, env, executor=None, self=self): self.string_it = 1 return None + act = SCons.Action.FunctionAction(build_it, - { 'strfunction' : string_it }) + {'strfunction': string_it}) r = act([], [], Environment()) assert r == 0, r assert self.build_it @@ -1593,15 +1700,15 @@ class FunctionActionTestCase(unittest.TestCase): pass func_matches = { - (2,7) : bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), - (3,5) : bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), - (3,6) : bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), + (2, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), + (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), + (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), } meth_matches = { - (2,7) : bytearray(b'1, 1, 0, 0,(),(),(d\x00\x00S),(),()'), - (3,5) : bytearray(b'1, 1, 0, 0,(),(),(d\x00\x00S),(),()'), - (3,6) : bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'), + (2, 7): bytearray(b'1, 1, 0, 0,(),(),(d\x00\x00S),(),()'), + (3, 5): bytearray(b'1, 1, 0, 0,(),(),(d\x00\x00S),(),()'), + (3, 6): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'), } def factory(act, **kw): @@ -1609,18 +1716,20 @@ class FunctionActionTestCase(unittest.TestCase): a = factory(GlobalFunc) c = a.get_contents(target=[], source=[], env=Environment()) - assert c == func_matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(func_matches[sys.version_info[:2]]) - + assert c == func_matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + func_matches[sys.version_info[:2]]) a = factory(LocalFunc) c = a.get_contents(target=[], source=[], env=Environment()) - assert c == func_matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(func_matches[sys.version_info[:2]]) + assert c == func_matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + func_matches[sys.version_info[:2]]) matches_foo = func_matches[sys.version_info[:2]] + b'foo' a = factory(GlobalFunc, varlist=['XYZ']) c = a.get_contents(target=[], source=[], env=Environment()) - assert c == func_matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(func_matches[sys.version_info[:2]]) + assert c == func_matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + func_matches[sys.version_info[:2]]) # assert c in func_matches, repr(c) c = a.get_contents(target=[], source=[], env=Environment(XYZ='foo')) @@ -1631,8 +1740,8 @@ class FunctionActionTestCase(unittest.TestCase): a = factory(GlobalFunc, varlist='XYZ') c = a.get_contents(target=[], source=[], env=Environment()) # assert c in func_matches, repr(c) - assert c == func_matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(func_matches[sys.version_info[:2]]) - + assert c == func_matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + func_matches[sys.version_info[:2]]) c = a.get_contents(target=[], source=[], env=Environment(XYZ='foo')) assert c in matches_foo, repr(c) @@ -1640,6 +1749,7 @@ class FunctionActionTestCase(unittest.TestCase): class Foo(object): def get_contents(self, target, source, env): return b'xyzzy' + a = factory(Foo()) c = a.get_contents(target=[], source=[], env=Environment()) assert c == b'xyzzy', repr(c) @@ -1647,14 +1757,17 @@ class FunctionActionTestCase(unittest.TestCase): class LocalClass(object): def LocalMethod(self): pass + lc = LocalClass() a = factory(lc.LocalMethod) c = a.get_contents(target=[], source=[], env=Environment()) - assert c == meth_matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(meth_matches[sys.version_info[:2]]) + assert c == meth_matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + meth_matches[sys.version_info[:2]]) def test_strfunction(self): """Test the FunctionAction.strfunction() method """ + def func(): pass @@ -1673,13 +1786,16 @@ class FunctionActionTestCase(unittest.TestCase): s = a.strfunction(target=[], source=[], env=Environment()) assert s == 'function', s + class ListActionTestCase(unittest.TestCase): def test___init__(self): """Test creation of a list of subsidiary Actions """ + def func(): pass + a = SCons.Action.ListAction(["x", func, ["y", "z"]]) assert isinstance(a.list[0], SCons.Action.CommandAction) assert isinstance(a.list[1], SCons.Action.FunctionAction) @@ -1689,10 +1805,13 @@ class ListActionTestCase(unittest.TestCase): def test___str__(self): """Test the __str__() method for a list of subsidiary Actions """ - def f(target,source,env): + + def f(target, source, env): pass - def g(target,source,env): + + def g(target, source, env): pass + a = SCons.Action.ListAction([f, g, "XXX", f]) s = str(a) assert s == "f(target, source, env)\ng(target, source, env)\nXXX\nf(target, source, env)", s @@ -1700,10 +1819,13 @@ class ListActionTestCase(unittest.TestCase): def test_genstring(self): """Test the genstring() method for a list of subsidiary Actions """ - def f(target,source,env): + + def f(target, source, env): pass - def g(target,source,env,for_signature): + + def g(target, source, env, for_signature): return 'generated %s %s' % (target[0], source[0]) + g = SCons.Action.Action(g, generator=1) a = SCons.Action.ListAction([f, g, "XXX", f]) s = a.genstring(['foo.x'], ['bar.y'], Environment()) @@ -1713,11 +1835,13 @@ class ListActionTestCase(unittest.TestCase): """Test executing a list of subsidiary Actions """ self.inc = 0 - def f(target,source,env): + + def f(target, source, env): s = env['s'] s.inc = s.inc + 1 + a = SCons.Action.ListAction([f, f, f]) - a([], [], Environment(s = self)) + a([], [], Environment(s=self)) assert self.inc == 3, self.inc cmd2 = r'%s %s %s syzygy' % (_python_, act_py, outfile) @@ -1737,8 +1861,9 @@ class ListActionTestCase(unittest.TestCase): def __init__(self, target, source, env): with open(env['out'], 'a') as f: f.write("class2b\n") + act = SCons.Action.ListAction([cmd2, function2, class2a(), class2b]) - r = act([], [], Environment(out = outfile)) + r = act([], [], Environment(out=outfile)) assert isinstance(r.status, class2b), r.status c = test.read(outfile, 'r') assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c @@ -1746,18 +1871,21 @@ class ListActionTestCase(unittest.TestCase): def test_get_contents(self): """Test fetching the contents of a list of subsidiary Actions """ - self.foo=0 + self.foo = 0 + def gen(target, source, env, for_signature): s = env['s'] - s.foo=1 + s.foo = 1 return "y" + a = SCons.Action.ListAction(["x", SCons.Action.Action(gen, generator=1), "z"]) - c = a.get_contents(target=[], source=[], env=Environment(s = self)) - assert self.foo==1, self.foo + c = a.get_contents(target=[], source=[], env=Environment(s=self)) + assert self.foo == 1, self.foo assert c == b"xyz", c + class LazyActionTestCase(unittest.TestCase): def test___init__(self): """Test creation of a lazy-evaluation Action @@ -1776,8 +1904,10 @@ class LazyActionTestCase(unittest.TestCase): def test_genstring(self): """Test the lazy-evaluation Action genstring() method """ + def f(target, source, env): pass + a = SCons.Action.Action('$BAR') env1 = Environment(BAR=f, s=self) env2 = Environment(BAR='xxx', s=self) @@ -1789,15 +1919,17 @@ class LazyActionTestCase(unittest.TestCase): def test_execute(self): """Test executing a lazy-evaluation Action """ + def f(target, source, env): s = env['s'] - s.test=1 + s.test = 1 return 0 + a = SCons.Action.Action('$BAR') - a([], [], env=Environment(BAR = f, s = self)) + a([], [], env=Environment(BAR=f, s=self)) assert self.test == 1, self.test cmd = r'%s %s %s lazy' % (_python_, act_py, outfile) - a([], [], env=Environment(BAR = cmd, s = self)) + a([], [], env=Environment(BAR=cmd, s=self)) c = test.read(outfile, 'r') assert c == "act.py: 'lazy'\n", c @@ -1805,7 +1937,7 @@ class LazyActionTestCase(unittest.TestCase): """Test fetching the contents of a lazy-evaluation Action """ a = SCons.Action.Action("${FOO}") - env = Environment(FOO = [["This", "is", "a", "test"]]) + env = Environment(FOO=[["This", "is", "a", "test"]]) c = a.get_contents(target=[], source=[], env=env) assert c == b"This is a test", c @@ -1816,11 +1948,10 @@ class LazyActionTestCase(unittest.TestCase): def LocalFunc(): pass - func_matches = { - (2,7) : bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), - (3,5) : bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), - (3,6) : bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), + (2, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), + (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), + (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), } meth_matches = [ @@ -1831,27 +1962,26 @@ class LazyActionTestCase(unittest.TestCase): def factory(act, **kw): return SCons.Action.FunctionAction(act, kw) - a = SCons.Action.Action("${FOO}") - env = Environment(FOO = factory(GlobalFunc)) + env = Environment(FOO=factory(GlobalFunc)) c = a.get_contents(target=[], source=[], env=env) # assert c in func_matches, "Got\n"+repr(c)+"\nExpected one of \n"+"\n".join([repr(f) for f in func_matches]) - assert c == func_matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(func_matches[sys.version_info[:2]]) - + assert c == func_matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + func_matches[sys.version_info[:2]]) - - env = Environment(FOO = factory(LocalFunc)) + env = Environment(FOO=factory(LocalFunc)) c = a.get_contents(target=[], source=[], env=env) - assert c == func_matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(func_matches[sys.version_info[:2]]) + assert c == func_matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + func_matches[sys.version_info[:2]]) # matches_foo = [x + b"foo" for x in func_matches] matches_foo = func_matches[sys.version_info[:2]] + b'foo' - - env = Environment(FOO = factory(GlobalFunc, varlist=['XYZ'])) + env = Environment(FOO=factory(GlobalFunc, varlist=['XYZ'])) c = a.get_contents(target=[], source=[], env=env) - assert c == func_matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(func_matches[sys.version_info[:2]]) + assert c == func_matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + func_matches[sys.version_info[:2]]) env['XYZ'] = 'foo' c = a.get_contents(target=[], source=[], env=env) @@ -1861,36 +1991,37 @@ class LazyActionTestCase(unittest.TestCase): class ActionCallerTestCase(unittest.TestCase): def test___init__(self): """Test creation of an ActionCaller""" - ac = SCons.Action.ActionCaller(1, [2, 3], {'FOO' : 4, 'BAR' : 5}) + ac = SCons.Action.ActionCaller(1, [2, 3], {'FOO': 4, 'BAR': 5}) assert ac.parent == 1, ac.parent assert ac.args == [2, 3], ac.args - assert ac.kw == {'FOO' : 4, 'BAR' : 5}, ac.kw + assert ac.kw == {'FOO': 4, 'BAR': 5}, ac.kw def test_get_contents(self): """Test fetching the contents of an ActionCaller""" + def strfunc(): pass def LocalFunc(): pass - matches = { - (2,7) : b'd\x00\x00S', - (3,5) : b'd\x00\x00S', - (3,6) : b'd\x00S\x00', + (2, 7): b'd\x00\x00S', + (3, 5): b'd\x00\x00S', + (3, 6): b'd\x00S\x00', } - af = SCons.Action.ActionFactory(GlobalFunc, strfunc) ac = SCons.Action.ActionCaller(af, [], {}) c = ac.get_contents([], [], Environment()) - assert c == matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(matches[sys.version_info[:2]]) + assert c == matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + matches[sys.version_info[:2]]) af = SCons.Action.ActionFactory(LocalFunc, strfunc) ac = SCons.Action.ActionCaller(af, [], {}) c = ac.get_contents([], [], Environment()) - assert c == matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(matches[sys.version_info[:2]]) + assert c == matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + matches[sys.version_info[:2]]) class LocalActFunc(object): def __call__(self): @@ -1899,12 +2030,14 @@ class ActionCallerTestCase(unittest.TestCase): af = SCons.Action.ActionFactory(GlobalActFunc(), strfunc) ac = SCons.Action.ActionCaller(af, [], {}) c = ac.get_contents([], [], Environment()) - assert c == matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(matches[sys.version_info[:2]]) + assert c == matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + matches[sys.version_info[:2]]) af = SCons.Action.ActionFactory(LocalActFunc(), strfunc) ac = SCons.Action.ActionCaller(af, [], {}) c = ac.get_contents([], [], Environment()) - assert c == matches[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected one of \n"+repr(matches[sys.version_info[:2]]) + assert c == matches[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected one of \n" + repr( + matches[sys.version_info[:2]]) matches = [ b"", @@ -1922,12 +2055,14 @@ class ActionCallerTestCase(unittest.TestCase): def test___call__(self): """Test calling an ActionCaller""" actfunc_args = [] + def actfunc(a1, a2, a3, args=actfunc_args): args.extend([a1, a2, a3]) + def strfunc(a1, a2, a3): pass - e = Environment(FOO = 2, BAR = 5) + e = Environment(FOO=2, BAR=5) af = SCons.Action.ActionFactory(actfunc, strfunc) ac = SCons.Action.ActionCaller(af, ['$__env__', '$FOO', 3], {}) @@ -1937,7 +2072,7 @@ class ActionCallerTestCase(unittest.TestCase): assert actfunc_args[2] == 3, actfunc_args del actfunc_args[:] - ac = SCons.Action.ActionCaller(af, [], {'a3' : '$__env__', 'a2' : '$BAR', 'a1' : 4}) + ac = SCons.Action.ActionCaller(af, [], {'a3': '$__env__', 'a2': '$BAR', 'a1': 4}) ac([], [], e) assert actfunc_args[0] == 4, actfunc_args assert actfunc_args[1] == '5', actfunc_args @@ -1947,29 +2082,35 @@ class ActionCallerTestCase(unittest.TestCase): def test_strfunction(self): """Test calling the ActionCaller strfunction() method""" strfunc_args = [] + def actfunc(a1, a2, a3, a4): pass + def strfunc(a1, a2, a3, a4, args=strfunc_args): args.extend([a1, a2, a3, a4]) af = SCons.Action.ActionFactory(actfunc, strfunc) ac = SCons.Action.ActionCaller(af, [1, '$FOO', 3, '$WS'], {}) - ac.strfunction([], [], Environment(FOO = 2, WS='white space')) + ac.strfunction([], [], Environment(FOO=2, WS='white space')) assert strfunc_args == [1, '2', 3, 'white space'], strfunc_args del strfunc_args[:] - d = {'a3' : 6, 'a2' : '$BAR', 'a1' : 4, 'a4' : '$WS'} + d = {'a3': 6, 'a2': '$BAR', 'a1': 4, 'a4': '$WS'} ac = SCons.Action.ActionCaller(af, [], d) - ac.strfunction([], [], Environment(BAR = 5, WS='w s')) + ac.strfunction([], [], Environment(BAR=5, WS='w s')) assert strfunc_args == [4, '5', 6, 'w s'], strfunc_args + class ActionFactoryTestCase(unittest.TestCase): def test___init__(self): """Test creation of an ActionFactory""" + def actfunc(): pass + def strfunc(): pass + ac = SCons.Action.ActionFactory(actfunc, strfunc) assert ac.actfunc is actfunc, ac.actfunc assert ac.strfunc is strfunc, ac.strfunc @@ -1978,10 +2119,13 @@ class ActionFactoryTestCase(unittest.TestCase): """Test calling whatever's returned from an ActionFactory""" actfunc_args = [] strfunc_args = [] + def actfunc(a1, a2, a3, args=actfunc_args): args.extend([a1, a2, a3]) + def strfunc(a1, a2, a3, args=strfunc_args): args.extend([a1, a2, a3]) + af = SCons.Action.ActionFactory(actfunc, strfunc) af(3, 6, 9)([], [], Environment()) assert actfunc_args == [3, 6, 9], actfunc_args @@ -1995,8 +2139,8 @@ class ActionCompareTestCase(unittest.TestCase): Basically ensures we can locate the builder, comparing it to itself along the way.""" - bar = SCons.Builder.Builder(action = {}) - env = Environment( BUILDERS = {'BAR' : bar} ) + bar = SCons.Builder.Builder(action={}) + env = Environment(BUILDERS={'BAR': bar}) name = bar.get_name(env) assert name == 'BAR', name @@ -2005,12 +2149,12 @@ class ActionCompareTestCase(unittest.TestCase): Ensure that we can compare builders (and thereby actions) to each other safely.""" - foo = SCons.Builder.Builder(action = '$FOO', suffix = '.foo') - bar = SCons.Builder.Builder(action = {}) + foo = SCons.Builder.Builder(action='$FOO', suffix='.foo') + bar = SCons.Builder.Builder(action={}) assert foo != bar assert foo.action != bar.action - env = Environment( BUILDERS = {'FOO' : foo, - 'BAR' : bar} ) + env = Environment(BUILDERS={'FOO': foo, + 'BAR': bar}) name = foo.get_name(env) assert name == 'FOO', name name = bar.get_name(env) @@ -2024,14 +2168,14 @@ class ActionCompareTestCase(unittest.TestCase): where one of the builders has a suffix dictionary with a None key.""" - foo = SCons.Builder.Builder(action = '$FOO', suffix = '.foo') - bar = SCons.Builder.Builder(action = {}, suffix={None:'.bar'}) + foo = SCons.Builder.Builder(action='$FOO', suffix='.foo') + bar = SCons.Builder.Builder(action={}, suffix={None: '.bar'}) bar.add_action('.cow', "$MOO") - dog = SCons.Builder.Builder(suffix = '.bar') + dog = SCons.Builder.Builder(suffix='.bar') - env = Environment( BUILDERS = {'FOO' : foo, - 'BAR' : bar, - 'DOG' : dog} ) + env = Environment(BUILDERS={'FOO': foo, + 'BAR': bar, + 'DOG': dog}) assert foo.get_name(env) == 'FOO', foo.get_name(env) assert bar.get_name(env) == 'BAR', bar.get_name(env) @@ -2040,9 +2184,11 @@ class ActionCompareTestCase(unittest.TestCase): class TestClass(object): """A test class used by ObjectContentsTestCase.test_object_contents""" + def __init__(self): self.a = "a" self.b = "b" + def method(self, arg): pass @@ -2058,14 +2204,14 @@ class ObjectContentsTestCase(unittest.TestCase): # Since the python bytecode has per version differences, we need different expected results per version expected = { - (2,7) : bytearray(b'3, 3, 0, 0,(),(),(|\x00\x00S),(),()'), - (3,5) : bytearray(b'3, 3, 0, 0,(),(),(|\x00\x00S),(),()'), - (3,6) : bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'), + (2, 7): bytearray(b'3, 3, 0, 0,(),(),(|\x00\x00S),(),()'), + (3, 5): bytearray(b'3, 3, 0, 0,(),(),(|\x00\x00S),(),()'), + (3, 6): bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'), } c = SCons.Action._function_contents(func1) - assert c == expected[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected \n"+"\n"+repr(expected[sys.version_info[:2]]) - + assert c == expected[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected \n" + "\n" + repr( + expected[sys.version_info[:2]]) def test_object_contents(self): """Test that Action._object_contents works""" @@ -2078,12 +2224,16 @@ class ObjectContentsTestCase(unittest.TestCase): # Since the python bytecode has per version differences, we need different expected results per version expected = { - (2,7): bytearray(b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01\x00|\x00\x00_\x00\x00d\x02\x00|\x00\x00_\x01\x00d\x00\x00S),(),(),2, 2, 0, 0,(),(),(d\x00\x00S),(),()}}{{{a=a,b=b}}}"), - (3,5): bytearray(b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01\x00|\x00\x00_\x00\x00d\x02\x00|\x00\x00_\x01\x00d\x00\x00S),(),(),2, 2, 0, 0,(),(),(d\x00\x00S),(),()}}{{{a=a,b=b}}}"), - (3,6): bytearray(b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"), + (2, 7): bytearray( + b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01\x00|\x00\x00_\x00\x00d\x02\x00|\x00\x00_\x01\x00d\x00\x00S),(),(),2, 2, 0, 0,(),(),(d\x00\x00S),(),()}}{{{a=a,b=b}}}"), + (3, 5): bytearray( + b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01\x00|\x00\x00_\x00\x00d\x02\x00|\x00\x00_\x01\x00d\x00\x00S),(),(),2, 2, 0, 0,(),(),(d\x00\x00S),(),()}}{{{a=a,b=b}}}"), + (3, 6): bytearray( + b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"), } - assert c == expected[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected \n"+"\n"+repr(expected[sys.version_info[:2]]) + assert c == expected[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected \n" + "\n" + repr( + expected[sys.version_info[:2]]) def test_code_contents(self): """Test that Action._code_contents works""" @@ -2093,36 +2243,17 @@ class ObjectContentsTestCase(unittest.TestCase): # Since the python bytecode has per version differences, we need different expected results per version expected = { - (2,7) : bytearray(b'0, 0, 0, 0,(N.),(),(d\x00\x00GHd\x01\x00S)'), - (3,5) : bytearray(b'0, 0, 0, 0,(N.),(print),(e\x00\x00d\x00\x00\x83\x01\x00\x01d\x01\x00S)'), - (3,6) : bytearray(b'0, 0, 0, 0,(N.),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'), + (2, 7): bytearray(b'0, 0, 0, 0,(N.),(),(d\x00\x00GHd\x01\x00S)'), + (3, 5): bytearray(b'0, 0, 0, 0,(N.),(print),(e\x00\x00d\x00\x00\x83\x01\x00\x01d\x01\x00S)'), + (3, 6): bytearray(b'0, 0, 0, 0,(N.),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'), } - assert c == expected[sys.version_info[:2]], "Got\n"+repr(c)+"\nExpected \n"+"\n"+expected[sys.version_info[:2]] - + assert c == expected[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected \n" + "\n" + expected[ + sys.version_info[:2]] if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ _ActionActionTestCase, - ActionTestCase, - CommandActionTestCase, - CommandGeneratorActionTestCase, - FunctionActionTestCase, - ListActionTestCase, - LazyActionTestCase, - ActionCallerTestCase, - ActionFactoryTestCase, - ActionCompareTestCase, - ObjectContentsTestCase ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - - TestUnit.run(suite) - - # Swap this for above to debug otherwise you can't run individual tests as TestUnit is swallowing arguments - # unittest.main() + unittest.main() # Local Variables: # tab-width:4 -- cgit v0.12 From babf25c3aeec87f4bb99c22ed3793a1b27c5ff45 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 22 Jun 2018 20:31:41 -0700 Subject: Update CHANGES.txt to sort contributor list by last name alphabetically. Also added info on bug fix in this branch. --- src/CHANGES.txt | 53 ++++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index b0baecb..a603755 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -7,26 +7,9 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - From Philipp Maierhöfer - - Added a __hash__ method to the class Scons.Subst.Literal. Required when substituting Literal - objects when SCons runs with Python 3. - - Added missing FORTRANMODDIRPREFIX to the gfortran tool. - - From Richard West: - - Add SConstruct.py, Sconstruct.py, sconstruct.py to the search path for the root SConstruct file. - Allows easier debugging within Visual Studio - - Change setup.py to change the install directory (via pip, or setup.py install) from scons-#.#.# - to scons (Yielding /lib/scons/SCons/ instead of /lib/scons/SCons-#.#.#/). - This changes SCons to better comply with normal Python installation practices. - From Bernard Blackham: - Fixed handling of side-effects in task master (fixes #3013). - From Daniel Moody: - - Set the pickling protocal back to highest which was causing issues - with variant dir tests. This will cause issues if reading sconsigns - pickled with the previous lower protocal. - From Ray Donnelly: - Fix the PATH created by scons.bat (and other .bat files) to provide a normalized PATH. Some pythons in the 3.6 series are no longer able to handle paths which @@ -45,19 +28,47 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - Re-Enable parallel SCons (-j) when running via Pypy - Move SCons test framework files to testing/framework and remove all references to QMtest. QMTest has not been used by SCons for some time now. + - Fixed issue causing stack trace when python Action function contains a unicode string when being + run with Python 2.7 From Andrew Featherstone - Removed unused --warn options from the man page and source code. + From Philipp Maierhöfer + - Added a __hash__ method to the class Scons.Subst.Literal. Required when substituting Literal + objects when SCons runs with Python 3. + - Added missing FORTRANMODDIRPREFIX to the gfortran tool. + + From Fredrik Medley: + - Fix exception when printing of EnviromentError messages. + Specifically, this fixes error reporting of the race condition when + initializing the cache which error previously was hidden. + From Daniel Moody: - Updated Jar builder to handle nodes and directories better - Updated Jar builder to flatten source list which could contain embedded lists - Removed some magic numbers from jar.py on behalf of Mats Wichmann (mats@linux.com) + - Set the pickling protocal back to highest which was causing issues + with variant dir tests. This will cause issues if reading sconsigns + pickled with the previous lower protocal. + From Gary Oberbrunner: - Fix bug when Installing multiple subdirs outside the source tree - fix to_str to handle None without raising exception + From Jonathon Reinhart: + - Replace all instances of `int main()` in C code with `int main(void)`. + Specifically, this fixes the test cases use by Configure.CheckCC() which + would fail when using -Wstrict-prototypes. + + From Richard West: + - Add SConstruct.py, Sconstruct.py, sconstruct.py to the search path for the root SConstruct file. + Allows easier debugging within Visual Studio + - Change setup.py to change the install directory (via pip, or setup.py install) from scons-#.#.# + to scons (Yielding /lib/scons/SCons/ instead of /lib/scons/SCons-#.#.#/). + This changes SCons to better comply with normal Python installation practices. + From Mats Wichmann: - Updated manpage scons.xml to fix a nested list problem - Updated doc terminiology: use prepend instead of append as appropriate @@ -65,15 +76,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - update wiki links to new github location - update bug links to new github location - From Jonathon Reinhart: - - Replace all instances of `int main()` in C code with `int main(void)`. - Specifically, this fixes the test cases use by Configure.CheckCC() which - would fail when using -Wstrict-prototypes. - From Fredrik Medley: - - Fix exception when printing of EnviromentError messages. - Specifically, this fixes error reporting of the race condition when - initializing the cache which error previously was hidden. RELEASE 3.0.1 - Mon, 12 Nov 2017 15:31:33 -0700 -- cgit v0.12 From c0b5e62acfa7d4b611a20f26b415af664c22cc88 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Tue, 26 Jun 2018 10:08:57 -0700 Subject: Add test for unicode strings it Configure Contexts TryAction calls --- src/engine/SCons/SConfTests.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index 65c4b5f..e19abaf 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -300,10 +300,15 @@ int main(void) { """Test SConf.TryAction """ def actionOK(target, source, env): - open(str(target[0]), "w").write( "RUN OK\n" ) + open(str(target[0]), "w").write("RUN OK\n") return None def actionFAIL(target, source, env): return 1 + def actionUnicode(target, source, env): + open(str(target[0]), "wb").write('2\302\242\n') + return None + + self._resetSConfState() sconf = self.SConf.SConf(self.scons_env, conf_dir=self.test.workpath('config.tests'), @@ -313,6 +318,11 @@ int main(void) { assert ret and output.encode('utf-8') == bytearray("RUN OK"+os.linesep,'utf-8'), (ret, output) (ret, output) = sconf.TryAction(action=actionFAIL) assert not ret and output == "", (ret, output) + + # GH Issue #3141 - unicode text and py2.7 crashes. + (ret, output) = sconf.TryAction(action=actionUnicode) + assert ret and output == u'2\xa2\n', (ret, output) + finally: sconf.Finish() @@ -779,8 +789,7 @@ int main(void) { if __name__ == "__main__": - suite = unittest.makeSuite(SConfTestCase, 'test_') - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 -- cgit v0.12 From 1a80d7b28f8d13798fceff57463c2223c8908c1e Mon Sep 17 00:00:00 2001 From: William Deegan Date: Tue, 26 Jun 2018 10:10:57 -0700 Subject: Fix Issue #3141. Unicode in TryAction crashing. Use get_text_contents instead of get_contents().decode() (get_contents should always be bytes, but not always decode()'able (without specifying encoding) --- src/CHANGES.txt | 1 + src/engine/SCons/SConf.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index b0baecb..4be9068 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -45,6 +45,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - Re-Enable parallel SCons (-j) when running via Pypy - Move SCons test framework files to testing/framework and remove all references to QMtest. QMTest has not been used by SCons for some time now. + - Fix GH Issue #3141 unicode string in a TryAction() with python 2.7 crashes. From Andrew Featherstone - Removed unused --warn options from the man page and source code. diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py index 48eed8e..a14127e 100644 --- a/src/engine/SCons/SConf.py +++ b/src/engine/SCons/SConf.py @@ -609,7 +609,7 @@ class SConfBase(object): ok = self.TryBuild(self.env.SConfActionBuilder, text, extension) del self.env['BUILDERS']['SConfActionBuilder'] if ok: - outputStr = self.lastTarget.get_contents().decode() + outputStr = self.lastTarget.get_text_contents() return (1, outputStr) return (0, "") -- cgit v0.12 From 8245f89ad9f5bc564586ff58d680623161810177 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Tue, 26 Jun 2018 10:16:26 -0700 Subject: Fix new test so it only runs with py2.7 as that's the reported crashing environment --- src/engine/SCons/SConfTests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index e19abaf..dd5b8b9 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -319,9 +319,10 @@ int main(void) { (ret, output) = sconf.TryAction(action=actionFAIL) assert not ret and output == "", (ret, output) - # GH Issue #3141 - unicode text and py2.7 crashes. - (ret, output) = sconf.TryAction(action=actionUnicode) - assert ret and output == u'2\xa2\n', (ret, output) + if not TestCmd.IS_PY3: + # GH Issue #3141 - unicode text and py2.7 crashes. + (ret, output) = sconf.TryAction(action=actionUnicode) + assert ret and output == u'2\xa2\n', (ret, output) finally: sconf.Finish() -- cgit v0.12 From 5d6bfb7b1a498f7ef0ebc93bc93fc26fb89b9a34 Mon Sep 17 00:00:00 2001 From: MatthewMarinets Date: Thu, 28 Jun 2018 11:23:32 -0700 Subject: -Adjusted JavaCommon to deal with implementations of classes as constructor arguments. -Added a test to JavaCommonTests to test the new functionality. --- src/engine/SCons/Tool/JavaCommon.py | 97 ++++++++++++++++++++++++++++---- src/engine/SCons/Tool/JavaCommonTests.py | 43 ++++++++++++++ 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/src/engine/SCons/Tool/JavaCommon.py b/src/engine/SCons/Tool/JavaCommon.py index dfb9e33..7a065ca 100644 --- a/src/engine/SCons/Tool/JavaCommon.py +++ b/src/engine/SCons/Tool/JavaCommon.py @@ -37,6 +37,10 @@ java_parsing = 1 default_java_version = '1.4' +# a switch for which jdk versions to use the Scope state for smarter +# anonymous inner class parsing. +scopeStateVersions = ('1.8') + if java_parsing: # Parse Java files for class names. # @@ -64,7 +68,7 @@ if java_parsing: interfaces, and anonymous inner classes.""" def __init__(self, version=default_java_version): - if not version in ('1.1', '1.2', '1.3','1.4', '1.5', '1.6', '1.7', + if not version in ('1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '5', '6'): msg = "Java version %s not supported" % version raise NotImplementedError(msg) @@ -115,8 +119,8 @@ if java_parsing: ret = SkipState(1, self) self.skipState = ret return ret - - def __getAnonStack(self): + + def _getAnonStack(self): return self.anonStacksStack[-1] def openBracket(self): @@ -125,15 +129,16 @@ if java_parsing: def closeBracket(self): self.brackets = self.brackets - 1 if len(self.stackBrackets) and \ - self.brackets == self.stackBrackets[-1]: + self.brackets == self.stackBrackets[-1]: self.listOutputs.append('$'.join(self.listClasses)) self.localClasses.pop() self.listClasses.pop() self.anonStacksStack.pop() self.stackBrackets.pop() if len(self.stackAnonClassBrackets) and \ - self.brackets == self.stackAnonClassBrackets[-1]: - self.__getAnonStack().pop() + self.brackets == self.stackAnonClassBrackets[-1] and \ + self.version not in scopeStateVersions: + self._getAnonStack().pop() self.stackAnonClassBrackets.pop() def parseToken(self, token): @@ -175,17 +180,83 @@ if java_parsing: self.stackAnonClassBrackets.append(self.brackets) className = [] className.extend(self.listClasses) - self.__getAnonStack()[-1] = self.__getAnonStack()[-1] + 1 - for anon in self.__getAnonStack(): + self._getAnonStack()[-1] = self._getAnonStack()[-1] + 1 + for anon in self._getAnonStack(): className.append(str(anon)) self.listOutputs.append('$'.join(className)) self.nextAnon = self.nextAnon + 1 - self.__getAnonStack().append(0) + self._getAnonStack().append(0) def setPackage(self, package): self.package = package + class ScopeState(object): + """ + A state that parses code within a scope normally, + within the confines of a scope. + """ + def __init__(self, old_state): + self.outer_state = old_state.outer_state + self.old_state = old_state + self.brackets = 0 + + def __getClassState(self): + try: + return self.classState + except AttributeError: + ret = ClassState(self) + self.classState = ret + return ret + + def __getAnonClassState(self): + try: + return self.anonState + except AttributeError: + ret = SkipState(1, AnonClassState(self)) + self.anonState = ret + return ret + + def __getSkipState(self): + try: + return self.skipState + except AttributeError: + ret = SkipState(1, self) + self.skipState = ret + return ret + + def openBracket(self): + self.brackets = self.brackets + 1 + + def closeBracket(self): + self.brackets = self.brackets - 1 + + def parseToken(self, token): + # if self.brackets == 0: + # return self.old_state.parseToken(token) + if token[:2] == '//': + return IgnoreState('\n', self) + elif token == '/*': + return IgnoreState('*/', self) + elif token == '{': + self.openBracket() + elif token == '}': + self.closeBracket() + if self.brackets == 0: + self.outer_state._getAnonStack().pop() + return self.old_state + elif token in ['"', "'"]: + return IgnoreState(token, self) + elif token == "new": + # anonymous inner class + return self.__getAnonClassState() + elif token == '.': + # Skip the attribute, it might be named "class", in which + # case we don't want to treat the following token as + # an inner class name... + return self.__getSkipState() + return self + class AnonClassState(object): """A state that looks for anonymous inner classes.""" def __init__(self, old_state): @@ -212,13 +283,15 @@ if java_parsing: if token == 'new': # look further for anonymous inner class return SkipState(1, AnonClassState(self)) - elif token in [ '"', "'" ]: + elif token in ['"', "'"]: return IgnoreState(token, self) elif token == ')': self.brace_level = self.brace_level - 1 return self if token == '{': self.outer_state.addAnonClass() + if self.outer_state.version in scopeStateVersions: + return ScopeState(old_state = self.old_state).parseToken(token) return self.old_state.parseToken(token) class SkipState(object): @@ -247,8 +320,8 @@ if java_parsing: # 'Foo$1Inner' # https://github.com/SCons/scons/issues/2087 if self.outer_state.localClasses and \ - self.outer_state.stackBrackets[-1] > \ - self.outer_state.stackBrackets[-2]+1: + self.outer_state.stackBrackets[-1] > \ + self.outer_state.stackBrackets[-2]+1: locals = self.outer_state.localClasses[-1] try: idx = locals[token] diff --git a/src/engine/SCons/Tool/JavaCommonTests.py b/src/engine/SCons/Tool/JavaCommonTests.py index 902030d..7e6a482 100644 --- a/src/engine/SCons/Tool/JavaCommonTests.py +++ b/src/engine/SCons/Tool/JavaCommonTests.py @@ -566,6 +566,49 @@ public class Foo assert expect == classes, (expect, classes) + def test_in_function_class_declaration(self): + """ + Test that implementing a class in a function call doesn't confuse SCons. + """ + + input = """ +package com.Matthew; + +public class AnonDemo { + + public static void main(String[] args) { + new AnonDemo().execute(); + } + + public void execute() { + Foo bar = new Foo(new Foo() { + @Override + public int getX() { return this.x; } + }) { + @Override + public int getX() { return this.x; } + }; + } + + public abstract class Foo { + public int x; + public abstract int getX(); + + public Foo(Foo f) { + this.x = f.x; + } + + public Foo() {} + } +} +""" + expect = ['AnonDemo$1', + 'AnonDemo$2', + 'AnonDemo$Foo', + 'AnonDemo'] + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.8') + assert expect == classes, (expect, classes) + if __name__ == "__main__": suite = unittest.TestSuite() -- cgit v0.12 From f1f2d14e1c9d155691675fc17e7d7a47f63b02f4 Mon Sep 17 00:00:00 2001 From: MatthewMarinets <31861583+MatthewMarinets@users.noreply.github.com> Date: Tue, 3 Jul 2018 09:35:31 -0700 Subject: Update CHANGES.txt --- src/CHANGES.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index b0baecb..5ecf245 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -6,7 +6,11 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - + + From Matthew Marinets + - Fixed an issue that caused the Java emitter to incorrectly parse arguments to constructors that + implemented a class. + From Philipp Maierhöfer - Added a __hash__ method to the class Scons.Subst.Literal. Required when substituting Literal objects when SCons runs with Python 3. -- cgit v0.12 From b86c6d4c6ba1ff86897cbbed37ec5c228dfec40e Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 6 Jul 2018 16:39:52 -0700 Subject: Change TestSCons's get_platform_python_info() to use the python which runtest.py was run with instead of blindly searching for python vis test.where_is(). This fixes an issue where the swig tests work fine with py3.6 but not with py2.7 on a win64 box with both installed, runtest launched via py -2 runtest.py ... --- testing/framework/TestSCons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index c32e06a..e625865 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -1262,7 +1262,7 @@ SConscript( sconscript ) this platform and its associated include path, library path, and library name. """ - python = self.where_is('python') + python = os.environ.get('python_executable',self.where_is('python')) if not python: self.skip_test('Can not find installed "python", skipping test.\n') -- cgit v0.12 From 77298ddfa1c2cce17fe722bdccad93fb93f9618a Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 8 Jul 2018 11:36:54 -0700 Subject: stop using obsolete distutils.sysconfig to find python include, link paths,and library names. Use sysconfig.get_config_var() calls instead --- test/SWIG/recursive-includes-cpp.py | 5 +++-- testing/framework/TestSCons.py | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/test/SWIG/recursive-includes-cpp.py b/test/SWIG/recursive-includes-cpp.py index 2c74cc8..b943406 100644 --- a/test/SWIG/recursive-includes-cpp.py +++ b/test/SWIG/recursive-includes-cpp.py @@ -78,7 +78,7 @@ if TestCmd.IS_WINDOWS: else: TARGET_ARCH = "" test.write('SConstruct', """\ -import distutils.sysconfig +import sysconfig import sys import os @@ -88,7 +88,7 @@ env = Environment( '-python' ], CPPPATH = [ - distutils.sysconfig.get_python_inc() + sysconfig.get_config_var("INCLUDEPY") ], SHLIBPREFIX = "", tools = [ 'default', 'swig' ] @@ -96,6 +96,7 @@ env = Environment( if sys.platform == 'darwin': env['LIBS']=['python%d.%d'%(sys.version_info[0],sys.version_info[1])] + env.Append(LIBPATH=[sysconfig.get_config_var("LIBDIR")]) elif sys.platform == 'win32': env.Append(LIBS=['python%d%d'%(sys.version_info[0],sys.version_info[1])]) env.Append(LIBPATH=[os.path.dirname(sys.executable) + "/libs"]) diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index e625865..767570d 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -1261,13 +1261,16 @@ SConscript( sconscript ) Returns a path to a Python executable suitable for testing on this platform and its associated include path, library path, and library name. + Returns: + (path to python, include path, library path, library name) """ python = os.environ.get('python_executable',self.where_is('python')) if not python: self.skip_test('Can not find installed "python", skipping test.\n') - self.run(program = python, stdin = """\ -import os, sys + if sys.platform == 'win32': + self.run(program = python, stdin = """\ +import sysconfig try: if sys.platform == 'win32': py_ver = 'python%d%d' % sys.version_info[:2] @@ -1288,6 +1291,13 @@ except: print(os.path.join(sys.prefix, 'include', py_ver)) print(os.path.join(sys.prefix, 'lib', py_ver, 'config')) print(py_ver) + """) + else: + self.run(program = python, stdin = """\ +import sysconfig +print(sysconfig.get_config_var("INCLUDEPY")) +print(sysconfig.get_config_var("LIBDIR")) +print("python"+sysconfig.get_config_var("LDVERSION")) """) return [python] + self.stdout().strip().split('\n') -- cgit v0.12 From f90b61a1eb5da911786db6b1606a6652457a4240 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 8 Jul 2018 12:04:06 -0700 Subject: Fix for py2.7 + 3.5+ --- test/SWIG/live.py | 1 + testing/framework/TestSCons.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/test/SWIG/live.py b/test/SWIG/live.py index 684cff1..7c4bdce 100644 --- a/test/SWIG/live.py +++ b/test/SWIG/live.py @@ -46,6 +46,7 @@ swig = test.where_is('swig') if not swig: test.skip_test('Can not find installed "swig", skipping test.\n') + python, python_include, python_libpath, python_lib = \ test.get_platform_python_info() Python_h = python_include + '/Python.h' diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index 767570d..c9de47a 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -1264,12 +1264,12 @@ SConscript( sconscript ) Returns: (path to python, include path, library path, library name) """ - python = os.environ.get('python_executable',self.where_is('python')) + python = os.environ.get('python_executable', self.where_is('python')) if not python: self.skip_test('Can not find installed "python", skipping test.\n') if sys.platform == 'win32': - self.run(program = python, stdin = """\ + self.run(program=python, stdin="""\ import sysconfig try: if sys.platform == 'win32': @@ -1293,11 +1293,14 @@ except: print(py_ver) """) else: - self.run(program = python, stdin = """\ -import sysconfig + self.run(program=python, stdin="""\ +import sys, sysconfig print(sysconfig.get_config_var("INCLUDEPY")) print(sysconfig.get_config_var("LIBDIR")) -print("python"+sysconfig.get_config_var("LDVERSION")) +py_library_ver = sysconfig.get_config_var("LDVERSION") +if not py_library_ver: + py_library_ver = '%d.%d' % sys.version_info[:2] +print("python"+py_library_ver) """) return [python] + self.stdout().strip().split('\n') -- cgit v0.12 From 8c9aa75f2069bd3c0d2369c404899d457e391d0c Mon Sep 17 00:00:00 2001 From: William Deegan Date: Tue, 10 Jul 2018 14:54:27 -0400 Subject: fix TestSCons.get_platform_python_info() to work on win32, had omitted some needed imports --- testing/framework/TestSCons.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index c9de47a..50942ab 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -1270,12 +1270,9 @@ SConscript( sconscript ) if sys.platform == 'win32': self.run(program=python, stdin="""\ -import sysconfig +import sysconfig, sys, os.path try: - if sys.platform == 'win32': py_ver = 'python%d%d' % sys.version_info[:2] - else: - py_ver = 'python%d.%d' % sys.version_info[:2] except AttributeError: py_ver = 'python' + sys.version[:3] # print include and lib path -- cgit v0.12 From 881dbcf2ef36b31be29ad6aff17ee54f04865329 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 11 Jul 2018 09:04:01 +0800 Subject: typo credit: https://stackoverflow.com/questions/39856184/does-scons-customized-decider-function-require-to-be-class-member --- doc/user/depends.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/depends.xml b/doc/user/depends.xml index f5f2270..8e548d2 100644 --- a/doc/user/depends.xml +++ b/doc/user/depends.xml @@ -518,7 +518,7 @@ cc -o hello hello.o Program('hello.c') def decide_if_changed(dependency, target, prev_ni): - if self.get_timestamp() != prev_ni.timestamp: + if dependency.get_timestamp() != prev_ni.timestamp: dep = str(dependency) tgt = str(target) if specific_part_of_file_has_changed(dep, tgt): -- cgit v0.12 From b8180bf3cbfdc23523a201f3c889291bdabc2c67 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Thu, 12 Jul 2018 08:42:03 +0800 Subject: Update CHANGES.txt --- src/CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index b3ef826..01418bc 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -97,6 +97,9 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - xml validity fixes from SConstruct.py change - update wiki links to new github location - update bug links to new github location + + From Hao Wu + - typo in customized decider example in user guide -- cgit v0.12 From 16c0be7f275d8270b1898b42c8e254f44cfea728 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Thu, 12 Jul 2018 14:08:08 +0800 Subject: Replace usage of unittest.TestSuite with unittest.main() --- src/engine/SCons/ActionTests.py | 1 - src/engine/SCons/CacheDirTests.py | 12 +----------- src/engine/SCons/DefaultsTests.py | 9 +-------- src/engine/SCons/ErrorsTests.py | 5 +---- src/engine/SCons/ExecutorTests.py | 9 +-------- src/engine/SCons/MemoizeTests.py | 12 +----------- src/engine/SCons/Node/AliasTests.py | 13 +------------ src/engine/SCons/Node/NodeTests.py | 12 +----------- src/engine/SCons/Node/PythonTests.py | 13 +------------ src/engine/SCons/Platform/PlatformTests.py | 13 +------------ src/engine/SCons/SConfTests.py | 2 -- src/engine/SCons/Scanner/DTests.py | 10 +--------- src/engine/SCons/Scanner/DirTests.py | 9 +-------- src/engine/SCons/Scanner/LaTeXTests.py | 11 +---------- src/engine/SCons/Scanner/ProgTests.py | 3 +-- src/engine/SCons/Scanner/RCTests.py | 3 +-- src/engine/SCons/Script/MainTests.py | 9 +-------- src/engine/SCons/SubstTests.py | 18 +----------------- src/engine/SCons/TaskmasterTests.py | 4 +--- src/engine/SCons/Tool/JavaCommonTests.py | 9 +-------- src/engine/SCons/Tool/javacTests.py | 9 +++------ src/engine/SCons/Tool/wixTests.py | 4 +--- src/engine/SCons/UtilTests.py | 1 - src/engine/SCons/Variables/BoolVariableTests.py | 5 +---- src/engine/SCons/Variables/EnumVariableTests.py | 5 +---- src/engine/SCons/Variables/ListVariableTests.py | 5 +---- src/engine/SCons/Variables/PackageVariableTests.py | 5 +---- src/engine/SCons/Variables/PathVariableTests.py | 5 +---- src/engine/SCons/Variables/VariablesTests.py | 13 ++++--------- src/engine/SCons/WarningsTests.py | 5 +---- 30 files changed, 32 insertions(+), 202 deletions(-) diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 2959ede..ed88f72 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -50,7 +50,6 @@ import SCons.Environment import SCons.Errors import TestCmd -import TestUnit # Initial setup of the common environment for all tests, # a temporary working directory containing a diff --git a/src/engine/SCons/CacheDirTests.py b/src/engine/SCons/CacheDirTests.py index 494db98..ef87746 100644 --- a/src/engine/SCons/CacheDirTests.py +++ b/src/engine/SCons/CacheDirTests.py @@ -29,7 +29,6 @@ import sys import unittest from TestCmd import TestCmd -import TestUnit import SCons.CacheDir @@ -287,16 +286,7 @@ class FileTestCase(BaseTestCase): SCons.CacheDir.CacheRetrieveSilent = save_CacheRetrieveSilent if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ - CacheDirTestCase, - FileTestCase, - ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) - + unittest.main() # Local Variables: # tab-width:4 # indent-tabs-mode:nil diff --git a/src/engine/SCons/DefaultsTests.py b/src/engine/SCons/DefaultsTests.py index 141243b..e04d1eb 100644 --- a/src/engine/SCons/DefaultsTests.py +++ b/src/engine/SCons/DefaultsTests.py @@ -32,7 +32,6 @@ import unittest from collections import UserDict import TestCmd -import TestUnit import SCons.Errors @@ -77,13 +76,7 @@ class DefaultsTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ DefaultsTestCase, - ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/ErrorsTests.py b/src/engine/SCons/ErrorsTests.py index 7819580..5c16160 100644 --- a/src/engine/SCons/ErrorsTests.py +++ b/src/engine/SCons/ErrorsTests.py @@ -28,8 +28,6 @@ import os import sys import unittest -import TestUnit - import SCons.Errors @@ -126,8 +124,7 @@ class ErrorsTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.makeSuite(ErrorsTestCase, 'test_') - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/ExecutorTests.py b/src/engine/SCons/ExecutorTests.py index eeab3ad..59ca5c7 100644 --- a/src/engine/SCons/ExecutorTests.py +++ b/src/engine/SCons/ExecutorTests.py @@ -26,8 +26,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import sys import unittest -import TestUnit - import SCons.Executor @@ -483,12 +481,7 @@ class ExecutorTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ ExecutorTestCase ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/MemoizeTests.py b/src/engine/SCons/MemoizeTests.py index 3ce9a11..726f656 100644 --- a/src/engine/SCons/MemoizeTests.py +++ b/src/engine/SCons/MemoizeTests.py @@ -26,8 +26,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import sys import unittest -import TestUnit - import SCons.Memoize # Enable memoization counting @@ -165,15 +163,7 @@ class CountValueTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ - CountDictTestCase, - CountValueTestCase, - ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Node/AliasTests.py b/src/engine/SCons/Node/AliasTests.py index 8e31875..5d9c799 100644 --- a/src/engine/SCons/Node/AliasTests.py +++ b/src/engine/SCons/Node/AliasTests.py @@ -26,8 +26,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import sys import unittest -import TestUnit - import SCons.Errors import SCons.Node.Alias @@ -113,16 +111,7 @@ class AliasBuildInfoTestCase(unittest.TestCase): bi = SCons.Node.Alias.AliasBuildInfo() if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ - AliasTestCase, - AliasBuildInfoTestCase, - AliasNodeInfoTestCase, - ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 3802f8c..ca6c883 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -30,8 +30,6 @@ import re import sys import unittest -import TestUnit - import SCons.Errors import SCons.Node import SCons.Util @@ -1349,15 +1347,7 @@ class NodeListTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ BuildInfoBaseTestCase, - NodeInfoBaseTestCase, - NodeTestCase, - NodeListTestCase ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Node/PythonTests.py b/src/engine/SCons/Node/PythonTests.py index 346542b..7ef9e52 100644 --- a/src/engine/SCons/Node/PythonTests.py +++ b/src/engine/SCons/Node/PythonTests.py @@ -26,8 +26,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import sys import unittest -import TestUnit - import SCons.Errors import SCons.Node.Python @@ -113,16 +111,7 @@ class ValueBuildInfoTestCase(unittest.TestCase): bi = SCons.Node.Python.ValueBuildInfo() if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ - ValueTestCase, - ValueBuildInfoTestCase, - ValueNodeInfoTestCase, - ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Platform/PlatformTests.py b/src/engine/SCons/Platform/PlatformTests.py index 3432e94..6f720ec 100644 --- a/src/engine/SCons/Platform/PlatformTests.py +++ b/src/engine/SCons/Platform/PlatformTests.py @@ -28,8 +28,6 @@ import SCons.compat import collections import unittest -import TestUnit - import SCons.Errors import SCons.Platform import SCons.Environment @@ -204,17 +202,8 @@ class PlatformEscapeTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.TestSuite() - - tclasses = [ PlatformTestCase, - TempFileMungeTestCase, - PlatformEscapeTestCase, - ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) + unittest.main() - TestUnit.run(suite) # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index dd5b8b9..cf8a7fb 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -33,8 +33,6 @@ from types import * import unittest import TestCmd -import TestUnit - sys.stdout = io.StringIO() diff --git a/src/engine/SCons/Scanner/DTests.py b/src/engine/SCons/Scanner/DTests.py index 51e527a..ad6bd5a 100644 --- a/src/engine/SCons/Scanner/DTests.py +++ b/src/engine/SCons/Scanner/DTests.py @@ -26,7 +26,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import unittest import TestCmd -import TestUnit import SCons.Scanner.D @@ -266,14 +265,7 @@ class DScannerTestCase(unittest.TestCase): self.helper('multiline.d', ['A.d']) if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ - DScannerTestCase, - ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Scanner/DirTests.py b/src/engine/SCons/Scanner/DirTests.py index 9701921..c433663 100644 --- a/src/engine/SCons/Scanner/DirTests.py +++ b/src/engine/SCons/Scanner/DirTests.py @@ -28,7 +28,6 @@ import sys import unittest import TestCmd -import TestUnit import SCons.Node.FS import SCons.Scanner.Dir @@ -121,14 +120,8 @@ class DirEntryScannerTestCase(DirScannerTestBase): sss = list(map(str, deps)) assert sss == [], sss -def suite(): - suite = unittest.TestSuite() - suite.addTest(DirScannerTestCase()) - suite.addTest(DirEntryScannerTestCase()) - return suite - if __name__ == "__main__": - TestUnit.run(suite()) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Scanner/LaTeXTests.py b/src/engine/SCons/Scanner/LaTeXTests.py index 213e89e..5913124 100644 --- a/src/engine/SCons/Scanner/LaTeXTests.py +++ b/src/engine/SCons/Scanner/LaTeXTests.py @@ -31,7 +31,6 @@ import sys import unittest import TestCmd -import TestUnit import SCons.Node.FS import SCons.Scanner.LaTeX @@ -156,16 +155,8 @@ class LaTeXScannerTestCase3(unittest.TestCase): files = ['inc5.xyz', 'subdir/inc4.eps'] deps_match(self, deps, files) - -def suite(): - suite = unittest.TestSuite() - suite.addTest(LaTeXScannerTestCase1()) - suite.addTest(LaTeXScannerTestCase2()) - suite.addTest(LaTeXScannerTestCase3()) - return suite - if __name__ == "__main__": - TestUnit.run(suite()) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Scanner/ProgTests.py b/src/engine/SCons/Scanner/ProgTests.py index e7c791c..45fdcb2 100644 --- a/src/engine/SCons/Scanner/ProgTests.py +++ b/src/engine/SCons/Scanner/ProgTests.py @@ -28,7 +28,6 @@ import sys import unittest import TestCmd -import TestUnit import SCons.Node.FS import SCons.Scanner.Prog @@ -274,7 +273,7 @@ def suite(): return suite if __name__ == "__main__": - TestUnit.run(suite()) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Scanner/RCTests.py b/src/engine/SCons/Scanner/RCTests.py index 2864026..d4f53ca 100644 --- a/src/engine/SCons/Scanner/RCTests.py +++ b/src/engine/SCons/Scanner/RCTests.py @@ -29,7 +29,6 @@ import collections import os import TestCmd -import TestUnit import SCons.Scanner.RC import SCons.Node.FS @@ -167,7 +166,7 @@ def suite(): return suite if __name__ == "__main__": - TestUnit.run(suite()) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Script/MainTests.py b/src/engine/SCons/Script/MainTests.py index fd6aaf4..aa6bfac 100644 --- a/src/engine/SCons/Script/MainTests.py +++ b/src/engine/SCons/Script/MainTests.py @@ -25,8 +25,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import unittest -import TestUnit - import SCons.Errors import SCons.Script.Main @@ -41,12 +39,7 @@ import SCons.Script.Main # of private functionality. if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/SubstTests.py b/src/engine/SCons/SubstTests.py index fcd77df..a111e4b 100644 --- a/src/engine/SCons/SubstTests.py +++ b/src/engine/SCons/SubstTests.py @@ -32,8 +32,6 @@ import unittest from collections import UserDict -import TestUnit - import SCons.Errors from SCons.Subst import * @@ -1243,21 +1241,7 @@ class subst_dict_TestCase(unittest.TestCase): assert SOURCES == ['s3', 'v-rstr-s4', 'v-s5'], SOURCES if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ - CLVar_TestCase, - LiteralTestCase, - SpecialAttrWrapperTestCase, - quote_spaces_TestCase, - scons_subst_TestCase, - scons_subst_list_TestCase, - scons_subst_once_TestCase, - subst_dict_TestCase, - ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py index d237d60..42ed00e 100644 --- a/src/engine/SCons/TaskmasterTests.py +++ b/src/engine/SCons/TaskmasterTests.py @@ -30,7 +30,6 @@ import copy import sys import unittest -import TestUnit import SCons.Taskmaster import SCons.Errors @@ -1239,8 +1238,7 @@ Taskmaster: No candidate anymore. if __name__ == "__main__": - suite = unittest.makeSuite(TaskmasterTestCase, 'test_') - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Tool/JavaCommonTests.py b/src/engine/SCons/Tool/JavaCommonTests.py index 902030d..cda785f 100644 --- a/src/engine/SCons/Tool/JavaCommonTests.py +++ b/src/engine/SCons/Tool/JavaCommonTests.py @@ -27,8 +27,6 @@ import os.path import sys import unittest -import TestUnit - import SCons.Scanner.IDL import SCons.Tool.JavaCommon @@ -568,12 +566,7 @@ public class Foo if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ parse_javaTestCase ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Tool/javacTests.py b/src/engine/SCons/Tool/javacTests.py index bf75d8a..96d41b2 100644 --- a/src/engine/SCons/Tool/javacTests.py +++ b/src/engine/SCons/Tool/javacTests.py @@ -24,8 +24,6 @@ import os import unittest -import TestUnit - import SCons.Tool.javac class DummyNode(object): @@ -40,14 +38,14 @@ class pathoptTestCase(unittest.TestCase): popt = SCons.Tool.javac.pathopt('-foopath', 'FOOPATH') env = {'FOOPATH': path} actual = popt(None, None, env, None) - self.assertEquals(expect, actual) + self.assertEqual(expect, actual) def assert_pathopt_default(self, expect, path, default): popt = SCons.Tool.javac.pathopt('-foopath', 'FOOPATH', default='DPATH') env = {'FOOPATH': path, 'DPATH': default} actual = popt(None, None, env, None) - self.assertEquals(expect, actual) + self.assertEqual(expect, actual) def test_unset(self): self.assert_pathopt([], None) @@ -101,5 +99,4 @@ class pathoptTestCase(unittest.TestCase): '') if __name__ == "__main__": - suite = unittest.makeSuite(pathoptTestCase, 'test_') - TestUnit.run(suite) + unittest.main() diff --git a/src/engine/SCons/Tool/wixTests.py b/src/engine/SCons/Tool/wixTests.py index c683e98..6039115 100644 --- a/src/engine/SCons/Tool/wixTests.py +++ b/src/engine/SCons/Tool/wixTests.py @@ -33,7 +33,6 @@ from SCons.Tool.wix import * from SCons.Environment import Environment import TestCmd -import TestUnit # create fake candle and light, so the tool's exists() method will succeed @@ -53,8 +52,7 @@ class WixTestCase(unittest.TestCase): assert env.subst('$WIXSRCSUF') == '.wxs' if __name__ == "__main__": - suite = unittest.makeSuite(WixTestCase, 'test_') - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 38b4da9..842d4d8 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -32,7 +32,6 @@ import unittest from collections import UserDict, UserList, UserString import TestCmd -import TestUnit import SCons.Errors diff --git a/src/engine/SCons/Variables/BoolVariableTests.py b/src/engine/SCons/Variables/BoolVariableTests.py index 7110f6f..3184407 100644 --- a/src/engine/SCons/Variables/BoolVariableTests.py +++ b/src/engine/SCons/Variables/BoolVariableTests.py @@ -26,8 +26,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import sys import unittest -import TestUnit - import SCons.Errors import SCons.Variables @@ -118,8 +116,7 @@ class BoolVariableTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.makeSuite(BoolVariableTestCase, 'test_') - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Variables/EnumVariableTests.py b/src/engine/SCons/Variables/EnumVariableTests.py index 931dfe2..4ecb8bd 100644 --- a/src/engine/SCons/Variables/EnumVariableTests.py +++ b/src/engine/SCons/Variables/EnumVariableTests.py @@ -26,8 +26,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import sys import unittest -import TestUnit - import SCons.Errors import SCons.Variables @@ -195,8 +193,7 @@ class EnumVariableTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.makeSuite(EnumVariableTestCase, 'test_') - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Variables/ListVariableTests.py b/src/engine/SCons/Variables/ListVariableTests.py index adfd353..ef4832c 100644 --- a/src/engine/SCons/Variables/ListVariableTests.py +++ b/src/engine/SCons/Variables/ListVariableTests.py @@ -27,8 +27,6 @@ import copy import sys import unittest -import TestUnit - import SCons.Errors import SCons.Variables @@ -125,8 +123,7 @@ class ListVariableTestCase(unittest.TestCase): n = l.__class__(copy.copy(l)) if __name__ == "__main__": - suite = unittest.makeSuite(ListVariableTestCase, 'test_') - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Variables/PackageVariableTests.py b/src/engine/SCons/Variables/PackageVariableTests.py index 3aa411d..cda3a4a 100644 --- a/src/engine/SCons/Variables/PackageVariableTests.py +++ b/src/engine/SCons/Variables/PackageVariableTests.py @@ -30,8 +30,6 @@ import SCons.Errors import SCons.Variables import TestCmd -import TestUnit - class PackageVariableTestCase(unittest.TestCase): def test_PackageVariable(self): @@ -115,8 +113,7 @@ class PackageVariableTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.makeSuite(PackageVariableTestCase, 'test_') - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Variables/PathVariableTests.py b/src/engine/SCons/Variables/PathVariableTests.py index 3d9d25c..48ad0e0 100644 --- a/src/engine/SCons/Variables/PathVariableTests.py +++ b/src/engine/SCons/Variables/PathVariableTests.py @@ -31,8 +31,6 @@ import SCons.Errors import SCons.Variables import TestCmd -import TestUnit - class PathVariableTestCase(unittest.TestCase): def test_PathVariable(self): @@ -228,8 +226,7 @@ class PathVariableTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.makeSuite(PathVariableTestCase, 'test_') - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Variables/VariablesTests.py b/src/engine/SCons/Variables/VariablesTests.py index 175a14b..43c785f 100644 --- a/src/engine/SCons/Variables/VariablesTests.py +++ b/src/engine/SCons/Variables/VariablesTests.py @@ -27,7 +27,6 @@ import sys import unittest import TestSCons -import TestUnit import SCons.Variables import SCons.Subst @@ -60,7 +59,9 @@ def check(key, value, env): def checkSave(file, expected): gdict = {} ldict = {} - exec(open(file, 'r').read(), gdict, ldict) + with open(file, 'r') as f: + exec(f.read(), gdict, ldict) + assert expected == ldict, "%s\n...not equal to...\n%s" % (expected, ldict) class VariablesTestCase(unittest.TestCase): @@ -690,13 +691,7 @@ class UnknownVariablesTestCase(unittest.TestCase): if __name__ == "__main__": - suite = unittest.TestSuite() - tclasses = [ VariablesTestCase, - UnknownVariablesTestCase ] - for tclass in tclasses: - names = unittest.getTestCaseNames(tclass, 'test_') - suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/WarningsTests.py b/src/engine/SCons/WarningsTests.py index 7b8d00d..b5c5aef 100644 --- a/src/engine/SCons/WarningsTests.py +++ b/src/engine/SCons/WarningsTests.py @@ -26,8 +26,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import sys import unittest -import TestUnit - import SCons.Warnings class TestOutput(object): @@ -127,8 +125,7 @@ class WarningsTestCase(unittest.TestCase): assert to.out == "Foo", to.out if __name__ == "__main__": - suite = unittest.makeSuite(WarningsTestCase, 'test_') - TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 -- cgit v0.12 From f808361cbad2b50e06d03add153931a4670b08a2 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Thu, 12 Jul 2018 14:29:01 +0800 Subject: add change blurb --- src/CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index b3ef826..2f77072 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -98,6 +98,9 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - update wiki links to new github location - update bug links to new github location + From Hao Wu + - Replace usage of unittest.TestSuite with unittest.main() (fix #3113) + -- cgit v0.12 From 73ea23d8c253aff31c933877fa69bc41da543aef Mon Sep 17 00:00:00 2001 From: William Deegan Date: Thu, 12 Jul 2018 12:12:04 -0400 Subject: Fix missing sys import --- testing/framework/TestSCons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index c9de47a..68ca56a 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -1270,7 +1270,7 @@ SConscript( sconscript ) if sys.platform == 'win32': self.run(program=python, stdin="""\ -import sysconfig +import sysconfig, sys try: if sys.platform == 'win32': py_ver = 'python%d%d' % sys.version_info[:2] -- cgit v0.12 From e00b7ef1f2f3f4bbc372d8b41c9d58c19a159d28 Mon Sep 17 00:00:00 2001 From: Gary Oberbrunner Date: Thu, 12 Jul 2018 07:37:38 -0400 Subject: Add mingw.py to MANIFEST This was causing bootstrap.py to fail on Linux. --- src/engine/MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 3f883be..6159c76 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -26,6 +26,7 @@ SCons/Platform/darwin.py SCons/Platform/hpux.py SCons/Platform/irix.py SCons/Platform/os2.py +SCons/Platform/mingw.py SCons/Platform/posix.py SCons/Platform/sunos.py SCons/Platform/win32.py -- cgit v0.12 From a29fca804cc209bc70c9aaddb0f51d5e81a5cac0 Mon Sep 17 00:00:00 2001 From: Gary Oberbrunner Date: Thu, 12 Jul 2018 08:03:48 -0400 Subject: Fix -jN for Python3.7, which always has thread support --- src/CHANGES.txt | 1 + src/engine/SCons/Script/Main.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 01418bc..4a050b0 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -78,6 +78,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE From Gary Oberbrunner: - Fix bug when Installing multiple subdirs outside the source tree - fix to_str to handle None without raising exception + - Fix -jN for python 3.7 From Jonathon Reinhart: - Replace all instances of `int main()` in C code with `int main(void)`. diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index 923ef1e..1d3fb54 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -1255,7 +1255,11 @@ def _build_targets(fs, options, targets, target_top): is_pypy = platform.python_implementation() == 'PyPy' - python_has_threads = sysconfig.get_config_var('WITH_THREAD') or is_pypy + # As of 3.7, python removed support for threadless platforms. + # See https://www.python.org/dev/peps/pep-0011/ + is_37_or_later = sys.version_info.major > 3 or \ + sys.version_info.major == 3 and sys.version_info.minor >= 7 + python_has_threads = sysconfig.get_config_var('WITH_THREAD') or is_pypy or is_37_or_later # to check if python configured with threads. global num_jobs num_jobs = options.num_jobs -- cgit v0.12 From 453f80b4c656daac96bb7c303621512716036842 Mon Sep 17 00:00:00 2001 From: Gary Oberbrunner Date: Thu, 12 Jul 2018 08:05:48 -0400 Subject: runtest.py: async is a reserved keyword in python 3.7 I don't think the async arg is used anywhere, but I kept it and just changed the name so the tests run. --- runtest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtest.py b/runtest.py index e5c5f2f..293e4ca 100755 --- a/runtest.py +++ b/runtest.py @@ -787,7 +787,7 @@ tests_passing = 0 tests_failing = 0 -def run_test(t, io_lock, async=True): +def run_test(t, io_lock, run_async=True): global tests_completed, tests_passing, tests_failing header = "" command_args = ['-tt'] @@ -930,4 +930,4 @@ else: # tab-width:4 # indent-tabs-mode:nil # End: -# vim: set expandtab tabstop=4 shiftwidth=4: \ No newline at end of file +# vim: set expandtab tabstop=4 shiftwidth=4: -- cgit v0.12 From 4f43aaeea0f59ef995e80adb22d2fc5fa1e5a80c Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 15 Jul 2018 14:17:24 -0400 Subject: Swap to use cleaner check for py 3.7 or above from @serhiy-storchaka --- src/engine/SCons/Script/Main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index 1d3fb54..6516a15 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -1257,8 +1257,7 @@ def _build_targets(fs, options, targets, target_top): is_pypy = platform.python_implementation() == 'PyPy' # As of 3.7, python removed support for threadless platforms. # See https://www.python.org/dev/peps/pep-0011/ - is_37_or_later = sys.version_info.major > 3 or \ - sys.version_info.major == 3 and sys.version_info.minor >= 7 + is_37_or_later = sys.version_info >= (3, 7) python_has_threads = sysconfig.get_config_var('WITH_THREAD') or is_pypy or is_37_or_later # to check if python configured with threads. global num_jobs -- cgit v0.12 From 0766c2974ac942240d0092c9afb0b168df031935 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 15 Jul 2018 14:58:34 -0400 Subject: For now disable 64 bit tests on appveyor. We burn a lot of time running both 32 and 64 bits. Enable python 3.7. --- .appveyor.yml | 49 +++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index fe231d2..31ec590 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -40,32 +40,41 @@ environment: - PYTHON: "C:\\Python36" BUILD_JOB_NUM: 4 - - PYTHON: "C:\\Python27-x64" + - PYTHON: "C:\\Python37" BUILD_JOB_NUM: 1 - - PYTHON: "C:\\Python27-x64" + - PYTHON: "C:\\Python37" BUILD_JOB_NUM: 2 - - PYTHON: "C:\\Python27-x64" + - PYTHON: "C:\\Python37" BUILD_JOB_NUM: 3 - - PYTHON: "C:\\Python27-x64" + - PYTHON: "C:\\Python37" BUILD_JOB_NUM: 4 - - PYTHON: "C:\\Python35-x64" - BUILD_JOB_NUM: 1 - - PYTHON: "C:\\Python35-x64" - BUILD_JOB_NUM: 2 - - PYTHON: "C:\\Python35-x64" - BUILD_JOB_NUM: 3 - - PYTHON: "C:\\Python35-x64" - BUILD_JOB_NUM: 4 + # - PYTHON: "C:\\Python27-x64" + # BUILD_JOB_NUM: 1 + # - PYTHON: "C:\\Python27-x64" + # BUILD_JOB_NUM: 2 + # - PYTHON: "C:\\Python27-x64" + # BUILD_JOB_NUM: 3 + # - PYTHON: "C:\\Python27-x64" + # BUILD_JOB_NUM: 4 + + # - PYTHON: "C:\\Python35-x64" + # BUILD_JOB_NUM: 1 + # - PYTHON: "C:\\Python35-x64" + # BUILD_JOB_NUM: 2 + # - PYTHON: "C:\\Python35-x64" + # BUILD_JOB_NUM: 3 + # - PYTHON: "C:\\Python35-x64" + # BUILD_JOB_NUM: 4 - - PYTHON: "C:\\Python36-x64" - BUILD_JOB_NUM: 1 - - PYTHON: "C:\\Python36-x64" - BUILD_JOB_NUM: 2 - - PYTHON: "C:\\Python36-x64" - BUILD_JOB_NUM: 3 - - PYTHON: "C:\\Python36-x64" - BUILD_JOB_NUM: 4 + # - PYTHON: "C:\\Python36-x64" + # BUILD_JOB_NUM: 1 + # - PYTHON: "C:\\Python36-x64" + # BUILD_JOB_NUM: 2 + # - PYTHON: "C:\\Python36-x64" + # BUILD_JOB_NUM: 3 + # - PYTHON: "C:\\Python36-x64" + # BUILD_JOB_NUM: 4 build: off build_script: -- cgit v0.12 From c822776c8ec66a42fbc6764a583e56d2cf1984ed Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 15 Jul 2018 19:45:39 -0400 Subject: Force lxml to build static. --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 31ec590..f17bedf 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,7 +4,7 @@ shallow_clone: true install: - "set PATH=%PYTHON%;%PYTHON%\\Scripts;C:\\cygwin64\\bin;C:\\msys64;%PATH%" - python --version - - pip install lxml + - STATIC_DEPS=true & pip install lxml - pip install pypiwin32 - choco install --allow-empty-checksums dmd - choco install --allow-empty-checksums ldc -- cgit v0.12 From 96cc018f721912d905e7b71b72de5747e0730210 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 15 Jul 2018 19:47:09 -0400 Subject: Fix set command --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index f17bedf..edc8e55 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,8 +4,8 @@ shallow_clone: true install: - "set PATH=%PYTHON%;%PYTHON%\\Scripts;C:\\cygwin64\\bin;C:\\msys64;%PATH%" - python --version - - STATIC_DEPS=true & pip install lxml - pip install pypiwin32 + - set STATIC_DEPS=true & pip install lxml - choco install --allow-empty-checksums dmd - choco install --allow-empty-checksums ldc - choco install --allow-empty-checksums swig -- cgit v0.12 From e0c8940559310988fb150b0ee8648da7200a1572 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Mon, 16 Jul 2018 10:27:22 -0400 Subject: Fix src/engine/SCons/ActionTests.py. Needed per python version valid strings to compare against for 3.7 --- src/engine/SCons/ActionTests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index ed88f72..b9cc00b 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -1525,6 +1525,7 @@ class CommandGeneratorActionTestCase(unittest.TestCase): (2, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), + (3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), } meth_matches = [ @@ -1702,12 +1703,15 @@ class FunctionActionTestCase(unittest.TestCase): (2, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), + (3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), + } meth_matches = { (2, 7): bytearray(b'1, 1, 0, 0,(),(),(d\x00\x00S),(),()'), (3, 5): bytearray(b'1, 1, 0, 0,(),(),(d\x00\x00S),(),()'), (3, 6): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'), + (3, 7): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'), } def factory(act, **kw): @@ -1951,6 +1955,7 @@ class LazyActionTestCase(unittest.TestCase): (2, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), (3, 5): bytearray(b'0, 0, 0, 0,(),(),(d\x00\x00S),(),()'), (3, 6): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), + (3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'), } meth_matches = [ @@ -2008,6 +2013,8 @@ class ActionCallerTestCase(unittest.TestCase): (2, 7): b'd\x00\x00S', (3, 5): b'd\x00\x00S', (3, 6): b'd\x00S\x00', + (3, 7): b'd\x00S\x00', + } af = SCons.Action.ActionFactory(GlobalFunc, strfunc) @@ -2206,6 +2213,7 @@ class ObjectContentsTestCase(unittest.TestCase): (2, 7): bytearray(b'3, 3, 0, 0,(),(),(|\x00\x00S),(),()'), (3, 5): bytearray(b'3, 3, 0, 0,(),(),(|\x00\x00S),(),()'), (3, 6): bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'), + (3, 7): bytearray(b'3, 3, 0, 0,(),(),(|\x00S\x00),(),()'), } c = SCons.Action._function_contents(func1) @@ -2229,6 +2237,8 @@ class ObjectContentsTestCase(unittest.TestCase): b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01\x00|\x00\x00_\x00\x00d\x02\x00|\x00\x00_\x01\x00d\x00\x00S),(),(),2, 2, 0, 0,(),(),(d\x00\x00S),(),()}}{{{a=a,b=b}}}"), (3, 6): bytearray( b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"), + (3, 7): bytearray( + b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"), } assert c == expected[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected \n" + "\n" + repr( @@ -2245,6 +2255,7 @@ class ObjectContentsTestCase(unittest.TestCase): (2, 7): bytearray(b'0, 0, 0, 0,(N.),(),(d\x00\x00GHd\x01\x00S)'), (3, 5): bytearray(b'0, 0, 0, 0,(N.),(print),(e\x00\x00d\x00\x00\x83\x01\x00\x01d\x01\x00S)'), (3, 6): bytearray(b'0, 0, 0, 0,(N.),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'), + (3, 7): bytearray(b'0, 0, 0, 0,(N.),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)'), } assert c == expected[sys.version_info[:2]], "Got\n" + repr(c) + "\nExpected \n" + "\n" + expected[ -- cgit v0.12 From 1e16a302b72ce5b5ecfc9b1c13588aec4a59daf3 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Tue, 17 Jul 2018 08:49:52 -0600 Subject: Fix typos from issue 3194 https://github.com/SCons/scons/issues/3149 Signed-off-by: Mats Wichmann --- doc/user/depends.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/user/depends.xml b/doc/user/depends.xml index 8e548d2..297eaa2 100644 --- a/doc/user/depends.xml +++ b/doc/user/depends.xml @@ -571,7 +571,7 @@ int main() { printf("Hello, world!\n"); } The content signature, or MD5 checksum, of the contents of the dependency - file the list time the ⌖ was built. + file the last time the ⌖ was built. @@ -583,7 +583,7 @@ int main() { printf("Hello, world!\n"); } The size in bytes of the dependency - file the list time the target was built. + file the last time the target was built. @@ -595,7 +595,7 @@ int main() { printf("Hello, world!\n"); } The modification time of the dependency - file the list time the ⌖ was built. + file the last time the ⌖ was built. -- cgit v0.12 From 58f4b2e91a62379b71246e4c882df82325c3f7fa Mon Sep 17 00:00:00 2001 From: Piotr Kasprzyk Date: Fri, 20 Jul 2018 16:04:30 +0200 Subject: Fix some misspellings, remove repeated: and Signed-off-by: Piotr Kasprzyk --- doc/user/environments.xml | 6 +++--- doc/user/parseflags.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/user/environments.xml b/doc/user/environments.xml index 2089dfe..65eed72 100644 --- a/doc/user/environments.xml +++ b/doc/user/environments.xml @@ -385,9 +385,9 @@ environment, of directory names, suffixes, etc. A &consenv; - is a distinct object creating within + is a distinct object created within a &SConscript; file and - and which contains values that + which contains values that affect how &SCons; decides what action to use to build a target, and even to define which targets @@ -1630,7 +1630,7 @@ env['ENV']['PATH'] = '/usr/local/bin:/bin:/usr/bin' But doing so makes your &SConscript; file less portable, (although in this case that may not be a huge concern - since the directories you list are likley system-specific, anyway). + since the directories you list are likely system-specific, anyway). diff --git a/doc/user/parseflags.xml b/doc/user/parseflags.xml index fa35d49..46d6866 100644 --- a/doc/user/parseflags.xml +++ b/doc/user/parseflags.xml @@ -57,7 +57,7 @@ &SCons; construction environments have a &ParseFlags; method that takes a set of typical command-line options - and distrbutes them into the appropriate construction variables. + and distributes them into the appropriate construction variables. Historically, it was created to support the &ParseConfig; method, so it focuses on options used by the GNU Compiler Collection (GCC) for the C and C++ toolchains. -- cgit v0.12 From bde85d7501235a67bee32c65f83a9e4da52e3968 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Tue, 24 Jul 2018 15:44:48 -0600 Subject: Fix py3 ResourceWarning in TestCmd class TestCmd method read() uses a shortcut to return data from a file, "return open(...).read()". Python 3 warns this is a resource leak because the file has no chance to be closed with the open being in the return statement. Split into two lines and use a context manager (with statement). Signed-off-by: Mats Wichmann --- src/CHANGES.txt | 1 + testing/framework/TestCmd.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 79d25db..e3a646c 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -102,6 +102,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - xml validity fixes from SConstruct.py change - update wiki links to new github location - update bug links to new github location + - convert TestCmd.read to use with statement on open (quiets 17 py3 warnings) From Hao Wu - typo in customized decider example in user guide diff --git a/testing/framework/TestCmd.py b/testing/framework/TestCmd.py index 9cd6b39..9499ff4 100644 --- a/testing/framework/TestCmd.py +++ b/testing/framework/TestCmd.py @@ -1247,9 +1247,11 @@ class TestCmd(object): if mode[0] != 'r': raise ValueError("mode must begin with 'r'") if IS_PY3 and 'b' not in mode: - return open(file, mode, newline=newline).read() + with open(file, mode, newline=newline) as f: + return f.read() else: - return open(file, mode).read() + with open(file, mode) as f: + return f.read() def rmdir(self, dir): """Removes the specified dir name. -- cgit v0.12 From 8308dd2ad4286ac3c9e882913451e9e517a0a238 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Tue, 24 Jul 2018 17:43:59 -0600 Subject: Quiet UtilTest ResourceWarning Use context manager around file opens to quiet a Python 3 resource leak warning. Signed-off-by: Mats Wichmann --- src/CHANGES.txt | 1 + src/engine/SCons/UtilTests.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index e3a646c..26ed803 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -103,6 +103,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - update wiki links to new github location - update bug links to new github location - convert TestCmd.read to use with statement on open (quiets 17 py3 warnings) + - quiet warning in UtilTests.py From Hao Wu - typo in customized decider example in user guide diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 842d4d8..81ec09d 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -488,8 +488,10 @@ class UtilTestCase(unittest.TestCase): filename = tempfile.mktemp() str = '1234567890 ' + filename try: - open(filename, 'w').write(str) - assert open(get_native_path(filename)).read() == str + with open(filename, 'w') as f: + f.write(str) + with open(get_native_path(filename)) as f: + assert f.read() == str finally: try: os.unlink(filename) -- cgit v0.12 From a27c072faaa7dd5eb598d1df031c34e2f4492571 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Wed, 25 Jul 2018 06:28:59 -0600 Subject: Change octal constants in QT tests Resolving two test failures in Python 3 caused by: http://www.python.org/dev/peps/pep-3127/: octal literals must now be specified with a leading "0o" or "0O" instead of "0"; Signed-off-by: Mats Wichmann --- src/CHANGES.txt | 1 + test/QT/generated-ui.py | 2 +- test/QT/up-to-date.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 26ed803..c875f1f 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -104,6 +104,7 @@ RELEASE 3.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE - update bug links to new github location - convert TestCmd.read to use with statement on open (quiets 17 py3 warnings) - quiet warning in UtilTests.py + - fix tests specifying octal constants for Py3 From Hao Wu - typo in customized decider example in user guide diff --git a/test/QT/generated-ui.py b/test/QT/generated-ui.py index e4632b1..4692a82 100644 --- a/test/QT/generated-ui.py +++ b/test/QT/generated-ui.py @@ -61,7 +61,7 @@ import SCons.Defaults def ExpHeaderScanner(node, env, path): return [] def generate(env): - HeaderAction=SCons.Action.Action([SCons.Defaults.Copy('$TARGET','$SOURCE'),SCons.Defaults.Chmod('$TARGET',0755)]) + HeaderAction=SCons.Action.Action([SCons.Defaults.Copy('$TARGET','$SOURCE'),SCons.Defaults.Chmod('$TARGET',0o755)]) HeaderBuilder= SCons.Builder.Builder(action=HeaderAction) env['BUILDERS']['ExportHeaders'] = HeaderBuilder def exists(env): diff --git a/test/QT/up-to-date.py b/test/QT/up-to-date.py index 21c758e..7a7e565 100644 --- a/test/QT/up-to-date.py +++ b/test/QT/up-to-date.py @@ -66,7 +66,7 @@ import SCons.Defaults def ExpHeaderScanner(node, env, path): return [] def generate(env): - HeaderAction=SCons.Action.Action([SCons.Defaults.Copy('$TARGET','$SOURCE'),SCons.Defaults.Chmod('$TARGET',0755)]) + HeaderAction=SCons.Action.Action([SCons.Defaults.Copy('$TARGET','$SOURCE'),SCons.Defaults.Chmod('$TARGET',0o755)]) HeaderBuilder= SCons.Builder.Builder(action=HeaderAction) env['BUILDERS']['ExportHeaders'] = HeaderBuilder def exists(env): -- cgit v0.12