http://scons.tigris.org/issues/show_bug.cgi?id=2345
[scons.git] / src / engine / SCons / SConfTests.py
index f9c144c32dd4c3a5cf8691eac9fb32e5c1ddd3ad..2b2380da60918db5a3fe604cb04c311a9a47e08c 100644 (file)
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import SCons.compat
+
+import io
 import os
 import re
-import string
-import StringIO
 import sys
 from types import *
 import unittest
 
 import TestCmd
 
-sys.stdout = StringIO.StringIO()
+sys.stdout = io.StringIO()
 
 if sys.platform == 'win32':
     existing_lib = "msvcrt"
@@ -44,10 +45,15 @@ class SConfTestCase(unittest.TestCase):
 
     def setUp(self):
         # we always want to start with a clean directory
+        self.save_cwd = os.getcwd()
         self.test = TestCmd.TestCmd(workdir = '') 
+        os.chdir(self.test.workpath(''))
 
     def tearDown(self):
         self.test.cleanup()
+        import SCons.SConsign
+        SCons.SConsign.Reset()
+        os.chdir(self.save_cwd)
 
     def _resetSConfState(self):
         # Ok, this is tricky, and i do not know, if everything is sane.
@@ -55,17 +61,15 @@ class SConfTestCase(unittest.TestCase):
         import SCons.SConsign
         SCons.SConsign.write() # simulate normal scons-finish
         for n in sys.modules.keys():
-            if string.split(n, '.')[0] == 'SCons':
+            if n.split('.')[0] == 'SCons' and n[:12] != 'SCons.compat':
                 m = sys.modules[n]
-                if type(m) is ModuleType:
+                if isinstance(m, ModuleType):
                     # if this is really a scons module, clear its namespace
                     del sys.modules[n]
                     m.__dict__.clear()
         # we only use SCons.Environment and SCons.SConf for these tests.
         import SCons.Environment
         import SCons.SConf
-        import SCons.Script.SConscript
-        SCons.Script.SConscript.sconscript_reading = 1
         self.Environment = SCons.Environment
         self.SConf = SCons.SConf
         # and we need a new environment, cause references may point to
@@ -75,27 +79,41 @@ class SConfTestCase(unittest.TestCase):
 
         # we want to do some autodetection here
         # this stuff works with
-        #    - cygwin on win32 (using cmd.exe, not bash)
+        #    - cygwin on Windows (using cmd.exe, not bash)
         #    - posix
-        #    - msvc on win32 (hopefully)
+        #    - msvc on Windows (hopefully)
         if (not self.scons_env.Detect( self.scons_env.subst('$CXX') ) or
             not self.scons_env.Detect( self.scons_env.subst('$CC') ) or
             not self.scons_env.Detect( self.scons_env.subst('$LINK') )):
-            raise Exception, "This test needs an installed compiler!"
+            raise Exception("This test needs an installed compiler!")
         if self.scons_env['CXX'] == 'g++':
             global existing_lib
             existing_lib = 'm'
-        
+
+        if sys.platform in ['cygwin', 'win32']:
+             # On Windows, SCons.Platform.win32 redefines the builtin
+             # file() and open() functions to close the file handles.
+             # This interferes with the unittest.py infrastructure in
+             # some way.  Just sidestep the issue by restoring the
+             # original builtin functions whenever we have to reset
+             # all of our global state.
+
+             import builtins
+             import SCons.Platform.win32
+
+             builtins.file = SCons.Platform.win32._builtin_file
+             builtins.open = SCons.Platform.win32._builtin_open
+
     def _baseTryXXX(self, TryFunc):
         # TryCompile and TryLink are much the same, so we can test them
         # in one method, we pass the function as a string ('TryCompile',
         # 'TryLink'), so we are aware of reloading modules.
         
         def checks(self, sconf, TryFuncString):
-            TryFunc = self.SConf.SConf.__dict__[TryFuncString]
-            res1 = TryFunc( sconf, "int main() { return 0; }", ".c" )
+            TryFunc = self.SConf.SConfBase.__dict__[TryFuncString]
+            res1 = TryFunc( sconf, "int main() { return 0; }\n", ".c" )
             res2 = TryFunc( sconf,
-                            '#include "no_std_header.h"\nint main() {return 0; }',
+                            '#include "no_std_header.h"\nint main() {return 0; }\n',
                             '.c' )
             return (res1,res2)
 
@@ -106,7 +124,7 @@ class SConfTestCase(unittest.TestCase):
                                  log_file=self.test.workpath('config.log'))
         try:
             res = checks( self, sconf, TryFunc )
-            assert res[0] and not res[1] 
+            assert res[0] and not res[1], res
         finally:
             sconf.Finish()
             
@@ -117,28 +135,29 @@ class SConfTestCase(unittest.TestCase):
                                  log_file=self.test.workpath('config.log'))
         try:
             res = checks( self, sconf, TryFunc )
-            assert res[0] and not res[1] 
+            assert res[0] and not res[1], res
         finally:
             sconf.Finish()
         # we should have exactly one one error cached 
         log = self.test.read( self.test.workpath('config.log') )
-        expr = re.compile( ".*(\(cached\))", re.DOTALL ) 
+        expr = re.compile( ".*failed in a previous run and all", re.DOTALL ) 
         firstOcc = expr.match( log )
-        assert firstOcc != None 
+        assert firstOcc is not None, log
         secondOcc = expr.match( log, firstOcc.end(0) )
-        assert secondOcc == None
+        assert secondOcc is None, log
 
         # 2.2 test the error caching mechanism (dependencies have changed)
         self._resetSConfState()
         sconf = self.SConf.SConf(self.scons_env,
                                  conf_dir=self.test.workpath('config.tests'),
                                  log_file=self.test.workpath('config.log'))
-        test_h = self.test.write( self.test.workpath('config.tests', 'no_std_header.h'),
-                                  "/* we are changing a dependency now */" );
+        no_std_header_h = self.test.workpath('config.tests', 'no_std_header.h')
+        test_h = self.test.write( no_std_header_h,
+                                  "/* we are changing a dependency now */\n" );
         try:
             res = checks( self, sconf, TryFunc )
             log = self.test.read( self.test.workpath('config.log') )
-            assert res[0] and res[1] 
+            assert res[0] and res[1], res
         finally:
             sconf.Finish()
 
@@ -150,7 +169,8 @@ class SConfTestCase(unittest.TestCase):
         sconf = self.SConf.SConf(self.scons_env,
                                  conf_dir=self.test.workpath('config.tests'),
                                  log_file=self.test.workpath('config.log'))
-        class MyBuilder:
+        import SCons.Builder
+        class MyBuilder(SCons.Builder.BuilderBase):
             def __init__(self):
                 self.prefix = ''
                 self.suffix = ''
@@ -159,8 +179,12 @@ class SConfTestCase(unittest.TestCase):
                     def __init__(self, name):
                         self.name = name
                         self.state = None
+                        self.waiting_parents = set()
                         self.side_effects = []
                         self.builder = None
+                        self.prerequisites = []
+                    def disambiguate(self):
+                        return self
                     def has_builder(self):
                         return 1
                     def add_pre_action(self, *actions):
@@ -181,16 +205,29 @@ class SConfTestCase(unittest.TestCase):
                         pass
                     def clear(self):
                         pass
-                    def current(self, calc=None):
+                    def is_up_to_date(self):
                         return None
                     def prepare(self):
                         pass
+                    def push_to_cache(self):
+                        pass
                     def retrieve_from_cache(self):
                         return 0
                     def build(self, **kw):
                         return
                     def built(self):
                         pass
+                    def get_stored_info(self):
+                        pass
+                    def do_not_store_info(self):
+                        pass
+                    def get_executor(self):
+                        class Executor:
+                            def __init__(self, targets):
+                                self.targets = targets
+                            def get_all_targets(self):
+                                return self.targets
+                        return Executor([self])
                 return [MyNode('n1'), MyNode('n2')]
         try:
             self.scons_env.Append(BUILDERS = {'SConfActionBuilder' : MyBuilder()})
@@ -220,7 +257,7 @@ int main() {
 }
 """
             res1 = sconf.TryRun( prog, ".c" ) 
-            res2 = sconf.TryRun( "not a c program", ".c" )
+            res2 = sconf.TryRun( "not a c program\n", ".c" )
             return (res1, res2)
         
         self._resetSConfState()
@@ -229,10 +266,11 @@ int main() {
                                  log_file=self.test.workpath('config.log'))
         try:
             res = checks(sconf)
-            assert res[0][0] and res[0][1] == "Hello" 
-            assert not res[1][0] and res[1][1] == ""
+            assert res[0][0] and res[0][1] == "Hello", res
+            assert not res[1][0] and res[1][1] == "", res
         finally:
             sconf.Finish()
+        log = self.test.read( self.test.workpath('config.log') )
 
         # test the caching mechanism
         self._resetSConfState()
@@ -241,24 +279,24 @@ int main() {
                                  log_file=self.test.workpath('config.log'))
         try:
             res = checks(sconf)
-            assert res[0][0] and res[0][1] == "Hello" 
-            assert not res[1][0] and res[1][1] == ""
+            assert res[0][0] and res[0][1] == "Hello", res
+            assert not res[1][0] and res[1][1] == "", res
         finally:
             sconf.Finish()
-        # we should have exactly one one error cached 
+        # we should have exactly one error cached 
         log = self.test.read( self.test.workpath('config.log') )
-        expr = re.compile( ".*(\(cached\))", re.DOTALL )
+        expr = re.compile( ".*failed in a previous run and all", re.DOTALL )
         firstOcc = expr.match( log )
-        assert firstOcc != None 
+        assert firstOcc is not None, log
         secondOcc = expr.match( log, firstOcc.end(0) )
-        assert secondOcc == None 
+        assert secondOcc is None, log
 
 
     def test_TryAction(self):
         """Test SConf.TryAction
         """
         def actionOK(target, source, env):
-            open(str(target[0]), "w").write( "RUN OK" )
+            open(str(target[0]), "w").write( "RUN OK\n" )
             return None
         def actionFAIL(target, source, env):
             return 1
@@ -268,9 +306,103 @@ int main() {
                                   log_file=self.test.workpath('config.log'))
         try:
             (ret, output) = sconf.TryAction(action=actionOK)
-            assert ret and output == "RUN OK"
+            assert ret and output == "RUN OK" + os.linesep, (ret, output)
             (ret, output) = sconf.TryAction(action=actionFAIL)
-            assert not ret and output == ""
+            assert not ret and output == "", (ret, output)
+        finally:
+            sconf.Finish()
+
+    def _test_check_compilers(self, comp, func, name):
+        """This is the implementation for CheckCC and CheckCXX tests."""
+        from copy import deepcopy
+
+        # Check that Check* works
+        r = func()
+        assert r, "could not find %s ?" % comp
+
+        # Check that Check* does fail if comp is not available in env
+        oldcomp = deepcopy(self.scons_env[comp])
+        del self.scons_env[comp]
+        r = func()
+        assert not r, "%s worked wo comp ?" % name
+
+        # Check that Check* does fail if comp is set but empty
+        self.scons_env[comp] = ''
+        r = func()
+        assert not r, "%s worked with comp = '' ?" % name
+
+        # Check that Check* does fail if comp is set to buggy executable
+        self.scons_env[comp] = 'thiscccompilerdoesnotexist'
+        r = func()
+        assert not r, "%s worked with comp = thiscompilerdoesnotexist ?" % name
+
+        # Check that Check* does fail if CFLAGS is buggy
+        self.scons_env[comp] = oldcomp
+        self.scons_env['%sFLAGS' % comp] = '/WX qwertyuiop.c'
+        r = func()
+        assert not r, "%s worked with %sFLAGS = qwertyuiop ?" % (name, comp)
+
+    def test_CheckCC(self):
+        """Test SConf.CheckCC()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'))
+        try:
+            try:
+                self._test_check_compilers('CC', sconf.CheckCC, 'CheckCC')
+            except AssertionError:
+                sys.stderr.write(self.test.read('config.log'))
+                raise
+        finally:
+            sconf.Finish()
+
+    def test_CheckSHCC(self):
+        """Test SConf.CheckSHCC()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'))
+        try:
+            try:
+                self._test_check_compilers('SHCC', sconf.CheckSHCC, 'CheckSHCC')
+            except AssertionError:
+                sys.stderr.write(self.test.read('config.log'))
+                raise
+        finally:
+            sconf.Finish()
+
+    def test_CheckCXX(self):
+        """Test SConf.CheckCXX()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'))
+        try:
+            try:
+                self._test_check_compilers('CXX', sconf.CheckCXX, 'CheckCXX')
+            except AssertionError:
+                sys.stderr.write(self.test.read('config.log'))
+                raise
+        finally:
+            sconf.Finish()
+
+    def test_CheckSHCXX(self):
+        """Test SConf.CheckSHCXX()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'))
+        try:
+            try:
+                self._test_check_compilers('SHCXX', sconf.CheckSHCXX, 'CheckSHCXX')
+            except AssertionError:
+                sys.stderr.write(self.test.read('config.log'))
+                raise
         finally:
             sconf.Finish()
 
@@ -375,7 +507,7 @@ int main() {
             def libs(env):
                 return env.get('LIBS', [])
 
-            env = sconf.env.Copy()
+            env = sconf.env.Clone()
 
             try:
                 r = sconf.CheckLib( existing_lib, "main", autoadd=1 )
@@ -384,7 +516,7 @@ int main() {
                 got = libs(sconf.env)
                 assert got == expect, "LIBS: expected %s, got %s" % (expect, got)
 
-                sconf.env = env.Copy()
+                sconf.env = env.Clone()
                 r = sconf.CheckLib( existing_lib, "main", autoadd=0 )
                 assert r, "did not find main in %s" % existing_lib
                 expect = libs(env)
@@ -392,7 +524,6 @@ int main() {
                 assert got == expect, "before and after LIBS were not the same"
             finally:
                 sconf.env = env
-
         finally:
             sconf.Finish()
 
@@ -439,7 +570,7 @@ int main() {
             def libs(env):
                 return env.get('LIBS', [])
 
-            env = sconf.env.Copy()
+            env = sconf.env.Clone()
 
             try:
                 r = sconf.CheckLibWithHeader( existing_lib, "math.h", "C", autoadd=1 )
@@ -448,7 +579,7 @@ int main() {
                 got = libs(sconf.env)
                 assert got == expect, "LIBS: expected %s, got %s" % (expect, got)
 
-                sconf.env = env.Copy()
+                sconf.env = env.Clone()
                 r = sconf.CheckLibWithHeader( existing_lib, "math.h", "C", autoadd=0 )
                 assert r, "did not find math.h with %s" % existing_lib
                 expect = libs(env)
@@ -472,12 +603,103 @@ int main() {
             # CheckFunc()
             r = sconf.CheckFunc('strcpy')
             assert r, "did not find strcpy"
+            r = sconf.CheckFunc('strcpy', '/* header */ char strcpy();')
+            assert r, "did not find strcpy"
             r = sconf.CheckFunc('hopefullynofunction')
             assert not r, "unexpectedly found hopefullynofunction"
 
         finally:
             sconf.Finish()
 
+    def test_Define(self):
+        """Test SConf.Define()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'),
+                                 config_h = self.test.workpath('config.h'))
+        try:
+            # XXX: we test the generated config.h string. This is not so good,
+            # ideally, we would like to test if the generated file included in
+            # a test program does what we want.
+
+            # Test defining one symbol wo value
+            sconf.config_h_text = ''
+            sconf.Define('YOP')
+            assert sconf.config_h_text == '#define YOP\n'
+
+            # Test defining one symbol with integer value
+            sconf.config_h_text = ''
+            sconf.Define('YOP', 1)
+            assert sconf.config_h_text == '#define YOP 1\n'
+
+            # Test defining one symbol with string value
+            sconf.config_h_text = ''
+            sconf.Define('YOP', '"YIP"')
+            assert sconf.config_h_text == '#define YOP "YIP"\n'
+
+            # Test defining one symbol with string value
+            sconf.config_h_text = ''
+            sconf.Define('YOP', "YIP")
+            assert sconf.config_h_text == '#define YOP YIP\n'
+
+        finally:
+            sconf.Finish()
+
+    def test_CheckTypeSize(self):
+        """Test SConf.CheckTypeSize()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'))
+        try:
+            # CheckTypeSize()
+
+            # In ANSI C, sizeof(char) == 1.
+            r = sconf.CheckTypeSize('char', expect = 1)
+            assert r == 1, "sizeof(char) != 1 ??"
+            r = sconf.CheckTypeSize('char', expect = 0)
+            assert r == 0, "sizeof(char) == 0 ??"
+            r = sconf.CheckTypeSize('char', expect = 2)
+            assert r == 0, "sizeof(char) == 2 ??"
+            r = sconf.CheckTypeSize('char')
+            assert r == 1, "sizeof(char) != 1 ??"
+            r = sconf.CheckTypeSize('const unsigned char')
+            assert r == 1, "sizeof(const unsigned char) != 1 ??"
+
+            # Checking C++
+            r = sconf.CheckTypeSize('const unsigned char', language = 'C++')
+            assert r == 1, "sizeof(const unsigned char) != 1 ??"
+
+            # Checking Non-existing type
+            r = sconf.CheckTypeSize('thistypedefhasnotchancetosexist_scons')
+            assert r == 0, \
+                   "Checking size of thistypedefhasnotchancetosexist_scons succeeded ?"
+
+        finally:
+            sconf.Finish()
+
+    def test_CheckDeclaration(self):
+        """Test SConf.CheckDeclaration()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'))
+        try:
+            # In ANSI C, malloc should be available in stdlib
+            r = sconf.CheckDeclaration('malloc', includes = "#include <stdlib.h>")
+            assert r, "malloc not declared ??"
+            # For C++, __cplusplus should be declared 
+            r = sconf.CheckDeclaration('__cplusplus', language = 'C++')
+            assert r, "__cplusplus not declared in C++ ??"
+            r = sconf.CheckDeclaration('__cplusplus', language = 'C')
+            assert not r, "__cplusplus declared  in C ??"
+        finally:
+            sconf.Finish()
+
     def test_(self):
         """Test SConf.CheckType()
         """
@@ -510,7 +732,7 @@ int main() {
 """
             (ret, output) = test.TryRun( prog, ".c" )
             test.Result( ret )
-            assert ret and output == "Hello" 
+            assert ret and output == "Hello", (ret, output)
             return ret
         
 
@@ -521,7 +743,7 @@ int main() {
                                  log_file=self.test.workpath('config.log'))
         try:
             ret = sconf.CheckCustom()
-            assert ret 
+            assert ret, ret
         finally:
             sconf.Finish()
             
@@ -532,3 +754,8 @@ if __name__ == "__main__":
     if not res.wasSuccessful():
         sys.exit(1)
 
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: