From 7f45d674f1d288349f48c8218487cc473ce736d9 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Thu, 20 Jul 2017 15:24:49 -0400 Subject: change comment to docstring as it should have been --- src/engine/SCons/Environment.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 85f9fa7..60a45e4 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -2368,19 +2368,21 @@ class OverrideEnvironment(Base): Environment = Base -# An entry point for returning a proxy subclass instance that overrides -# the subst*() methods so they don't actually perform construction -# variable substitution. This is specifically intended to be the shim -# layer in between global function calls (which don't want construction -# variable substitution) and the DefaultEnvironment() (which would -# substitute variables if left to its own devices).""" -# -# We have to wrap this in a function that allows us to delay definition of -# the class until it's necessary, so that when it subclasses Environment -# it will pick up whatever Environment subclass the wrapper interface -# might have assigned to SCons.Environment.Environment. def NoSubstitutionProxy(subject): + """ + An entry point for returning a proxy subclass instance that overrides + the subst*() methods so they don't actually perform construction + variable substitution. This is specifically intended to be the shim + layer in between global function calls (which don't want construction + variable substitution) and the DefaultEnvironment() (which would + substitute variables if left to its own devices). + + We have to wrap this in a function that allows us to delay definition of + the class until it's necessary, so that when it subclasses Environment + it will pick up whatever Environment subclass the wrapper interface + might have assigned to SCons.Environment.Environment. + """ class _NoSubstitutionProxy(Environment): def __init__(self, subject): self.__dict__['__subject'] = subject -- cgit v0.12 From e0bc3a04d5010e0c637edc59e034b91feaf343b6 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 30 Jul 2017 21:32:31 -0700 Subject: PY2/3 Fix action object content signiture to be stable. Note need expected value for each change in python bytecode. Currently that means one for each: py2.7, py3.5, py3.6 --- src/engine/SCons/Action.py | 67 ++++++++++++++++++++++++++++++++++++----- src/engine/SCons/ActionTests.py | 33 +++++++++----------- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 118c495..bf611af 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -105,6 +105,8 @@ import pickle import re import sys import subprocess +import itertools +import inspect import SCons.Debug from SCons.Debug import logInstanceCreation @@ -195,8 +197,11 @@ def _object_contents(obj): except AttributeError as ae: # Should be a pickle-able Python object. try: - return pickle.dumps(obj, ACTION_SIGNATURE_PICKLE_PROTOCOL) - except (pickle.PicklingError, TypeError, AttributeError): + return _object_instance_content(obj) + # pickling an Action instance or object doesn't yield a stable + # content as instance property may be dumped in different orders + # return pickle.dumps(obj, ACTION_SIGNATURE_PICKLE_PROTOCOL) + except (pickle.PicklingError, TypeError, AttributeError) as ex: # This is weird, but it seems that nested classes # are unpickable. The Python docs say it should # always be a PicklingError, but some Python @@ -308,15 +313,12 @@ def _function_contents(func): defaults.extend(b')') contents.append(defaults) - - # contents.append(bytearray(',(','utf-8') + b','.join(function_defaults_contents) + bytearray(')','utf-8')) else: contents.append(b',()') # The function contents depends on the closure captured cell values. closure = func.__closure__ or [] - #xxx = [_object_contents(x.cell_contents) for x in closure] try: closure_contents = [_object_contents(x.cell_contents) for x in closure] except AttributeError: @@ -325,10 +327,61 @@ def _function_contents(func): contents.append(b',(') contents.append(bytearray(b',').join(closure_contents)) contents.append(b')') - # contents.append(b'BBBBBBBB') - return bytearray(b'').join(contents) + retval = bytearray(b'').join(contents) + + # print("ReTVAL:%s"%retval) + return retval + +def _object_instance_content(obj): + """ + Returns consistant content for a action class or an instance thereof + :param obj: Should be either and action class or an instance thereof + :return: bytearray or bytes representing the obj suitable for generating + a signiture from. + """ + retval = bytearray() + + + inst_class = obj.__class__ + inst_class_name = bytearray(obj.__class__.__name__,'utf-8') + inst_class_module = bytearray(obj.__class__.__module__,'utf-8') + inst_class_hierarchy = bytearray(repr(inspect.getclasstree([obj.__class__,])),'utf-8') + # print("ICH:%s : %s"%(inst_class_hierarchy, repr(obj))) + + properties = [(p, getattr(obj, p, "None")) for p in dir(obj) if p[:2] != '__' and not inspect.ismethod(getattr(obj, p))] + properties.sort() + properties_str = ','.join(["%s=%s"%(p[0],p[1]) for p in properties]) + properties_bytes = bytearray(properties_str,'utf-8') + + methods = [p for p in dir(obj) if inspect.ismethod(getattr(obj, p))] + methods.sort() + + method_contents = [] + for m in methods: + # print("Method:%s"%m) + v = _function_contents(getattr(obj, m)) + # print("[%s->]V:%s [%s]"%(m,v,type(v))) + method_contents.append(v) + + retval = bytearray(b'{') + retval.extend(inst_class_name) + retval.extend(b":") + retval.extend(inst_class_module) + retval.extend(b'}[[') + retval.extend(inst_class_hierarchy) + retval.extend(b']]{{') + retval.extend(bytearray(b",").join(method_contents)) + retval.extend(b"}}") + return retval + + # print("class :%s"%inst_class) + # print("class_name :%s"%inst_class_name) + # print("class_module :%s"%inst_class_module) + # print("Class hier :\n%s"%pp.pformat(inst_class_hierarchy)) + # print("Inst Properties:\n%s"%pp.pformat(properties)) + # print("Inst Methods :\n%s"%pp.pformat(methods)) def _actionAppend(act1, act2): # This function knows how to slap two actions together. diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 34d9ffc..7980e39 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -98,7 +98,9 @@ scons_env = SCons.Environment.Environment() # Capture all the stuff the Actions will print, # so it doesn't clutter the output. -sys.stdout = io.StringIO() + +# TODO.. uncomment +# sys.stdout = io.StringIO() class CmdStringHolder(object): def __init__(self, cmd, literal=None): @@ -2054,7 +2056,7 @@ class ActionCompareTestCase(unittest.TestCase): assert dog.get_name(env) == 'DOG', dog.get_name(env) -class TestClass: +class TestClass(object): """A test class used by ObjectContentsTestCase.test_object_contents""" def __init__(self): self.a = "a" @@ -2081,7 +2083,6 @@ class ObjectContentsTestCase(unittest.TestCase): assert c in expected, "Got\n"+repr(c)+"\nExpected one of \n"+"\n".join([repr(e) for e in expected]) - # @unittest.skip("Results vary between py2 and py3, not sure if test makes sense to implement") def test_object_contents(self): """Test that Action._object_contents works""" @@ -2089,20 +2090,18 @@ class ObjectContentsTestCase(unittest.TestCase): o = TestClass() c = SCons.Action._object_contents(o) + # c = SCons.Action._object_instance_content(o) + + # Since the python bytecode has per version differences, we need different expected results per version if TestCmd.IS_PY3: - if TestCmd.IS_WINDOWS: - expected = [b'ccopy_reg\n_reconstructor\nq\x00(c__main__\nTestClass\nq\x01c__builtin__\nobject\nq\x02Ntq\x03Rq\x04}q\x05(X\x01\x00\x00\x00aq\x06h\x06X\x01\x00\x00\x00bq\x07h\x07ub.', # py 3.6 - b'ccopy_reg\n_reconstructor\nq\x00(c__main__\nTestClass\nq\x01c__builtin__\nobject\nq\x02Ntq\x03Rq\x04}q\x05(X\x01\x00\x00\x00bq\x06h\x06X\x01\x00\x00\x00aq\x07h\x07ub.', # py 3.5 - ] - else: - expected = [b'ccopy_reg\n_reconstructor\nq\x00(c__main__\nTestClass\nq\x01c__builtin__\nobject\nq\x02Ntq\x03Rq\x04}q\x05(X\x01\x00\x00\x00bq\x06h\x06X\x01\x00\x00\x00aq\x07h\x07ub.'] + if sys.version_info[:2] == (3,5): + expected = bytearray(b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,({str:builtins}[[[(, ()), [(, (,))]]]]{{}},{str:builtins}[[[(, ()), [(, (,))]]]]{{}}),({str:builtins}[[[(, ()), [(, (,))]]]]{{}},{str:builtins}[[[(, ()), [(, (,))]]]]{{}}),(d\x01\x00|\x00\x00_\x00\x00d\x02\x00|\x00\x00_\x01\x00d\x00\x00S),(),(),2, 2, 0, 0,(),(),(d\x00\x00S),(),()}}") + elif sys.version_info[:2] == (3,6): + expected = bytearray(b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,({str:builtins}[[[(, ()), [(, (,))]]]]{{}},{str:builtins}[[[(, ()), [(, (,))]]]]{{}}),({str:builtins}[[[(, ()), [(, (,))]]]]{{}},{str:builtins}[[[(, ()), [(, (,))]]]]{{}}),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}") else: - if TestCmd.IS_WINDOWS: - expected = [b'(c__main__\nTestClass\nq\x01oq\x02}q\x03(U\x01aU\x01aU\x01bU\x01bub.'] - else: - expected = [b'(c__main__\nTestClass\nq\x01oq\x02}q\x03(U\x01aU\x01aU\x01bU\x01bub.'] + expected = bytearray(b"{TestClass:__main__}[[[(, ()), [(, (,))]]]]{{1, 1, 0, 0,({str:__builtin__}[[[(, (,)), [(, (,))]]]]{{}},{str:__builtin__}[[[(, (,)), [(, (,))]]]]{{}}),({str:__builtin__}[[[(, (,)), [(, (,))]]]]{{}},{str:__builtin__}[[[(, (,)), [(, (,))]]]]{{}}),(d\x01\x00|\x00\x00_\x00\x00d\x02\x00|\x00\x00_\x01\x00d\x00\x00S),(),(),2, 2, 0, 0,(),(),(d\x00\x00S),(),()}}") - assert c in expected, "Got\n"+repr(c)+"\nExpected one of \n"+"\n".join([repr(e) for e in expected]) + assert c == expected, "Got\n"+repr(c)+"\nExpected \n"+"\n"+repr(expected) # @unittest.skip("Results vary between py2 and py3, not sure if test makes sense to implement") def test_code_contents(self): @@ -2122,9 +2121,6 @@ class ObjectContentsTestCase(unittest.TestCase): assert c in expected, "Got\n"+repr(c)+"\nExpected one of \n"+"\n".join([repr(e) for e in expected]) - - - if __name__ == "__main__": suite = unittest.TestSuite() tclasses = [ _ActionActionTestCase, @@ -2142,7 +2138,8 @@ if __name__ == "__main__": names = unittest.getTestCaseNames(tclass, 'test_') suite.addTests(list(map(tclass, names))) - TestUnit.run(suite) + # TestUnit.run(suite) + unittest.main() # Local Variables: # tab-width:4 -- cgit v0.12