3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 import SCons
.Taskmaster
42 def __init__(self
, name
, kids
=[], scans
=[]) -> None:
50 self
.prerequisites
= None
53 def targets(self
, node
):
56 self
.builder
= Builder()
59 self
.state
= SCons
.Node
.no_state
62 self
.waiting_parents
= set()
63 self
.waiting_s_e
= set()
64 self
.side_effect
= False
65 self
.side_effects
= []
67 self
.postprocessed
= None
70 self
.always_build
= None
72 def disambiguate(self
):
75 def push_to_cache(self
) -> None:
78 def retrieve_from_cache(self
) -> bool:
81 cache_text
.append(self
.name
+ " retrieved")
84 def make_ready(self
) -> None:
87 def prepare(self
) -> None:
91 def build(self
) -> None:
93 built_text
= self
.name
+ " built"
95 def remove(self
) -> None:
98 # The following four methods new_binfo(), del_binfo(),
99 # get_binfo(), clear() as well as its calls have been added
100 # to support the cached_execute() test (issue #2720).
101 # They are full copies (or snippets) of their actual
102 # counterparts in the Node class...
107 def del_binfo(self
) -> None:
108 """Delete the build info from this node."""
110 delattr(self
, 'binfo')
111 except AttributeError:
115 """Fetch a node's build information."""
118 except AttributeError:
121 binfo
= self
.new_binfo()
126 def clear(self
) -> None:
127 # The del_binfo() call here isn't necessary for normal execution,
128 # but is for interactive mode, where we might rebuild the same
129 # target and need to start from scratch.
132 def built(self
) -> None:
135 built_text
= built_text
+ " really"
137 # Clear the implicit dependency caches of any Nodes
138 # waiting for this Node to be built.
139 for parent
in self
.waiting_parents
:
140 parent
.implicit
= None
144 def release_target_info(self
) -> None:
147 def has_builder(self
) -> bool:
148 return self
.builder
is not None
150 def is_derived(self
) -> bool:
151 return self
.has_builder
or self
.side_effect
153 def alter_targets(self
):
154 return self
.alttargets
, None
156 def visited(self
) -> None:
158 visited_nodes
.append(self
.name
)
166 def scan(self
) -> None:
168 scan_called
= scan_called
+ 1
169 self
.kids
= self
.kids
+ self
.scans
172 def scanner_key(self
):
175 def add_to_waiting_parents(self
, node
) -> int:
176 wp
= self
.waiting_parents
185 def set_state(self
, state
) -> None:
188 def set_bsig(self
, bsig
) -> None:
191 def set_csig(self
, csig
) -> None:
194 def store_csig(self
) -> None:
197 def store_bsig(self
) -> None:
200 def is_up_to_date(self
) -> bool:
201 return self
._current
_val
203 def __str__(self
) -> str:
206 def postprocess(self
) -> None:
207 self
.postprocessed
= 1
208 self
.waiting_parents
= set()
210 def get_executor(self
):
211 if not hasattr(self
, 'executor'):
213 def prepare(self
) -> None:
216 def get_action_targets(self
):
219 def get_all_targets(self
):
222 def get_all_children(self
):
224 for node
in self
.targets
:
225 result
.extend(node
.children())
228 def get_all_prerequisites(self
):
231 def get_action_side_effects(self
):
234 self
.executor
= Executor()
235 self
.executor
.targets
= self
.targets
238 def get_internal_path(self
):
240 Should only be used (currently) by TaskmasterTestCase.test_cached_execute_target_unlink_fails
245 class OtherError(Exception):
249 class MyException(Exception):
253 class TaskmasterTestCase(unittest
.TestCase
):
255 def test_next_task(self
) -> None:
256 """Test fetching the next task
261 tm
= SCons
.Taskmaster
.Taskmaster([n1
, n1
])
270 n3
= Node("n3", [n1
, n2
])
272 tm
= SCons
.Taskmaster
.Taskmaster([n3
])
277 assert built_text
== "n1 built", built_text
284 assert built_text
== "n2 built", built_text
291 assert built_text
== "n3 built", built_text
295 assert tm
.next_task() is None
297 built_text
= "up to date: "
300 class MyTask(SCons
.Taskmaster
.AlwaysTask
):
301 def execute(self
) -> None:
303 if self
.targets
[0].get_state() == SCons
.Node
.up_to_date
:
305 built_text
= self
.targets
[0].name
+ " up-to-date top"
307 built_text
= self
.targets
[0].name
+ " up-to-date"
309 self
.targets
[0].build()
311 n1
.set_state(SCons
.Node
.no_state
)
313 n2
.set_state(SCons
.Node
.no_state
)
315 n3
.set_state(SCons
.Node
.no_state
)
317 tm
= SCons
.Taskmaster
.Taskmaster(targets
=[n3
], tasker
=MyTask
)
322 assert built_text
== "n1 up-to-date", built_text
329 assert built_text
== "n2 up-to-date", built_text
336 assert built_text
== "n3 up-to-date top", built_text
340 assert tm
.next_task() is None
344 n3
= Node("n3", [n1
, n2
])
346 n5
= Node("n5", [n3
, n4
])
347 tm
= SCons
.Taskmaster
.Taskmaster([n5
])
350 assert t1
.get_target() == n1
353 assert t2
.get_target() == n2
356 assert t4
.get_target() == n4
365 assert t3
.get_target() == n3
370 assert t5
.get_target() == n5
, t5
.get_target()
374 assert tm
.next_task() is None
377 n4
.set_state(SCons
.Node
.executed
)
378 tm
= SCons
.Taskmaster
.Taskmaster([n4
])
379 assert tm
.next_task() is None
382 n2
= Node("n2", [n1
])
383 tm
= SCons
.Taskmaster
.Taskmaster([n2
, n2
])
388 assert tm
.next_task() is None
392 n3
= Node("n3", [n1
], [n2
])
393 tm
= SCons
.Taskmaster
.Taskmaster([n3
])
395 target
= t
.get_target()
396 assert target
== n1
, target
400 target
= t
.get_target()
401 assert target
== n2
, target
405 target
= t
.get_target()
406 assert target
== n3
, target
409 assert tm
.next_task() is None
413 n3
= Node("n3", [n1
, n2
])
414 n4
= Node("n4", [n3
])
415 n5
= Node("n5", [n3
])
418 tm
= SCons
.Taskmaster
.Taskmaster([n4
])
420 assert t
.get_target() == n1
424 assert t
.get_target() == n2
428 assert t
.get_target() == n3
432 assert t
.get_target() == n4
435 assert tm
.next_task() is None
436 assert scan_called
== 4, scan_called
438 tm
= SCons
.Taskmaster
.Taskmaster([n5
])
440 assert t
.get_target() == n5
, t
.get_target()
442 assert tm
.next_task() is None
443 assert scan_called
== 5, scan_called
448 n4
= Node("n4", [n1
, n2
, n3
])
449 n5
= Node("n5", [n4
])
450 n3
.side_effect
= True
451 n1
.side_effects
= n2
.side_effects
= n3
.side_effects
= [n4
]
452 tm
= SCons
.Taskmaster
.Taskmaster([n1
, n2
, n3
, n4
, n5
])
454 assert t
.get_target() == n1
455 assert n4
.state
== SCons
.Node
.executing
, n4
.state
459 assert t
.get_target() == n2
463 assert t
.get_target() == n3
467 assert t
.get_target() == n4
471 assert t
.get_target() == n5
472 assert not tm
.next_task()
479 n4
= Node("n4", [n1
, n2
, n3
])
481 def reverse(dependencies
):
482 dependencies
.reverse()
485 tm
= SCons
.Taskmaster
.Taskmaster([n4
], order
=reverse
)
487 assert t
.get_target() == n3
, t
.get_target()
491 assert t
.get_target() == n2
, t
.get_target()
495 assert t
.get_target() == n1
, t
.get_target()
499 assert t
.get_target() == n4
, t
.get_target()
508 tm
= SCons
.Taskmaster
.Taskmaster([n5
])
510 assert t
.get_target() == n5
514 tm
= SCons
.Taskmaster
.Taskmaster([n6
])
516 assert t
.get_target() == n7
520 assert t
.get_target() == n6
525 n2
= Node("n2", [n1
])
526 n1
.set_state(SCons
.Node
.failed
)
527 tm
= SCons
.Taskmaster
.Taskmaster([n2
])
528 assert tm
.next_task() is None
532 n1
.targets
= [n1
, n2
]
534 tm
= SCons
.Taskmaster
.Taskmaster([n1
])
540 assert s
== SCons
.Node
.executed
, s
542 assert s
== SCons
.Node
.executed
, s
544 def test_make_ready_out_of_date(self
) -> None:
545 """Test the Task.make_ready() method's list of out-of-date Nodes
549 def TaskGen(tm
, targets
, top
, node
, ood
=ood
):
550 class MyTask(SCons
.Taskmaster
.AlwaysTask
):
551 def make_ready(self
) -> None:
552 SCons
.Taskmaster
.Task
.make_ready(self
)
553 self
.ood
.extend(self
.out_of_date
)
555 t
= MyTask(tm
, targets
, top
, node
)
568 tm
= SCons
.Taskmaster
.Taskmaster(targets
=[n1
, c2
, n3
, c4
, a5
],
573 assert ood
== [n1
], ood
577 assert ood
== [], ood
581 assert ood
== [n3
], ood
585 assert ood
== [], ood
589 assert ood
== [a5
], ood
591 def test_make_ready_exception(self
) -> None:
592 """Test handling exceptions from Task.make_ready()
595 class MyTask(SCons
.Taskmaster
.AlwaysTask
):
596 def make_ready(self
):
597 raise MyException("from make_ready()")
600 tm
= SCons
.Taskmaster
.Taskmaster(targets
=[n1
], tasker
=MyTask
)
602 exc_type
, exc_value
, exc_tb
= t
.exception
603 assert exc_type
== MyException
, repr(exc_type
)
604 assert str(exc_value
) == "from make_ready()", exc_value
606 def test_needs_execute(self
) -> None:
607 """Test that we can't instantiate a Task subclass without needs_execute
609 We should be getting:
610 TypeError: Can't instantiate abstract class MyTask with abstract methods needs_execute
613 class MyTask(SCons
.Taskmaster
.Task
):
617 tm
= SCons
.Taskmaster
.Taskmaster(targets
=[n1
], tasker
=MyTask
)
618 with self
.assertRaises(TypeError):
621 def test_make_ready_all(self
) -> None:
622 """Test the make_ready_all() method"""
624 class MyTask(SCons
.Taskmaster
.AlwaysTask
):
625 make_ready
= SCons
.Taskmaster
.Task
.make_ready_all
634 tm
= SCons
.Taskmaster
.Taskmaster(targets
=[n1
, c2
, n3
, c4
])
637 target
= t
.get_target()
638 assert target
is n1
, target
639 assert target
.state
== SCons
.Node
.executing
, target
.state
641 target
= t
.get_target()
642 assert target
is c2
, target
643 assert target
.state
== SCons
.Node
.up_to_date
, target
.state
645 target
= t
.get_target()
646 assert target
is n3
, target
647 assert target
.state
== SCons
.Node
.executing
, target
.state
649 target
= t
.get_target()
650 assert target
is c4
, target
651 assert target
.state
== SCons
.Node
.up_to_date
, target
.state
660 tm
= SCons
.Taskmaster
.Taskmaster(targets
=[n1
, c2
, n3
, c4
],
664 target
= t
.get_target()
665 assert target
is n1
, target
666 assert target
.state
== SCons
.Node
.executing
, target
.state
668 target
= t
.get_target()
669 assert target
is c2
, target
670 assert target
.state
== SCons
.Node
.executing
, target
.state
672 target
= t
.get_target()
673 assert target
is n3
, target
674 assert target
.state
== SCons
.Node
.executing
, target
.state
676 target
= t
.get_target()
677 assert target
is c4
, target
678 assert target
.state
== SCons
.Node
.executing
, target
.state
682 def test_children_errors(self
) -> None:
683 """Test errors when fetching the children of a node.
686 class StopNode(Node
):
688 raise SCons
.Errors
.StopError("stop!")
690 class ExitNode(Node
):
691 def children(self
) -> None:
695 tm
= SCons
.Taskmaster
.Taskmaster([n1
])
697 exc_type
, exc_value
, exc_tb
= t
.exception
698 assert exc_type
== SCons
.Errors
.StopError
, repr(exc_type
)
699 assert str(exc_value
) == "stop!", exc_value
702 tm
= SCons
.Taskmaster
.Taskmaster([n2
])
704 exc_type
, exc_value
= t
.exception
705 assert exc_type
== SCons
.Errors
.ExplicitExit
, repr(exc_type
)
706 assert exc_value
.node
== n2
, exc_value
.node
707 assert exc_value
.status
== 77, exc_value
.status
709 def test_cycle_detection(self
) -> None:
710 """Test detecting dependency cycles
713 n2
= Node("n2", [n1
])
714 n3
= Node("n3", [n2
])
717 tm
= SCons
.Taskmaster
.Taskmaster([n3
])
720 except SCons
.Errors
.UserError
as e
:
721 assert str(e
) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e
)
723 assert 'Did not catch expected UserError'
725 def test_next_top_level_candidate(self
) -> None:
726 """Test the next_top_level_candidate() method
729 n2
= Node("n2", [n1
])
730 n3
= Node("n3", [n2
])
732 tm
= SCons
.Taskmaster
.Taskmaster([n3
])
734 assert t
.targets
== [n1
], t
.targets
736 assert t
.targets
== [n3
], list(map(str, t
.targets
))
737 assert t
.top
== 1, t
.top
739 def test_stop(self
) -> None:
740 """Test the stop() method
742 Both default and overridden in a subclass.
748 n3
= Node("n3", [n1
, n2
])
750 tm
= SCons
.Taskmaster
.Taskmaster([n3
])
754 assert built_text
== "n1 built", built_text
757 assert built_text
== "n1 built really", built_text
760 assert tm
.next_task() is None
762 class MyTM(SCons
.Taskmaster
.Taskmaster
):
763 def stop(self
) -> None:
765 built_text
= "MyTM.stop()"
766 SCons
.Taskmaster
.Taskmaster
.stop(self
)
770 n3
= Node("n3", [n1
, n2
])
774 tm
.next_task().execute()
775 assert built_text
== "n1 built"
778 assert built_text
== "MyTM.stop()"
779 assert tm
.next_task() is None
781 def test_executed(self
) -> None:
782 """Test when a task has been executed
788 tm
= SCons
.Taskmaster
.Taskmaster([n1
])
792 n1
.set_state(SCons
.Node
.executing
)
797 assert s
== SCons
.Node
.executed
, s
798 assert built_text
== "xxx really", built_text
799 assert visited_nodes
== ['n1'], visited_nodes
802 tm
= SCons
.Taskmaster
.Taskmaster([n2
])
804 built_text
= "should_not_change"
812 assert built_text
== "should_not_change", built_text
813 assert visited_nodes
== ['n2'], visited_nodes
817 n3
.targets
= [n3
, n4
]
818 tm
= SCons
.Taskmaster
.Taskmaster([n3
])
821 n3
.set_state(SCons
.Node
.up_to_date
)
822 n4
.set_state(SCons
.Node
.executing
)
827 assert s
== SCons
.Node
.up_to_date
, s
829 assert s
== SCons
.Node
.executed
, s
830 assert visited_nodes
== ['n3', 'n4'], visited_nodes
832 def test_prepare(self
):
833 """Test preparation of multiple Nodes for a task
837 tm
= SCons
.Taskmaster
.Taskmaster([n1
, n2
])
839 # This next line is moderately bogus. We're just reaching
840 # in and setting the targets for this task to an array. The
841 # "right" way to do this would be to have the next_task() call
842 # set it up by having something that approximates a real Builder
843 # return this list--but that's more work than is probably
844 # warranted right now.
845 n1
.get_executor().targets
= [n1
, n2
]
852 tm
= SCons
.Taskmaster
.Taskmaster([n3
, n4
])
854 # More bogus reaching in and setting the targets.
855 n3
.set_state(SCons
.Node
.up_to_date
)
856 n3
.get_executor().targets
= [n3
, n4
]
861 # If the Node has had an exception recorded while it was getting
862 # prepared, then prepare() should raise that exception.
863 class MyException(Exception):
868 tm
= SCons
.Taskmaster
.Taskmaster([n5
])
870 t
.exception_set((MyException
, "exception value"))
872 exc_actually_caught
= None
876 except MyException
as e
:
879 except Exception as exc_actually_caught
:
881 assert exc_caught
, "did not catch expected MyException: %s" % exc_actually_caught
882 assert str(exc_value
) == "exception value", exc_value
883 assert built_text
is None, built_text
885 # Regression test, make sure we prepare not only
886 # all targets, but their side effects as well.
893 n6
.side_effects
= [n8
]
894 n7
.side_effects
= [n9
, n10
]
896 tm
= SCons
.Taskmaster
.Taskmaster([n6
, n7
])
898 # More bogus reaching in and setting the targets.
899 n6
.get_executor().targets
= [n6
, n7
]
907 # Make sure we call an Executor's prepare() method.
908 class ExceptionExecutor
:
910 raise Exception("Executor.prepare() exception")
912 def get_all_targets(self
):
915 def get_all_children(self
):
917 for node
in self
.nodes
:
918 result
.extend(node
.children())
921 def get_all_prerequisites(self
):
924 def get_action_side_effects(self
):
928 n11
.executor
= ExceptionExecutor()
929 n11
.executor
.nodes
= [n11
]
930 tm
= SCons
.Taskmaster
.Taskmaster([n11
])
934 except Exception as e
:
935 assert str(e
) == "Executor.prepare() exception", e
937 raise AssertionError("did not catch expected exception")
939 def test_execute(self
) -> None:
940 """Test executing a task
946 tm
= SCons
.Taskmaster
.Taskmaster([n1
])
949 assert built_text
== "n1 built", built_text
951 def raise_UserError():
952 raise SCons
.Errors
.UserError
955 n2
.build
= raise_UserError
956 tm
= SCons
.Taskmaster
.Taskmaster([n2
])
960 except SCons
.Errors
.UserError
:
963 self
.fail("did not catch expected UserError")
965 def raise_BuildError():
966 raise SCons
.Errors
.BuildError
969 n3
.build
= raise_BuildError
970 tm
= SCons
.Taskmaster
.Taskmaster([n3
])
974 except SCons
.Errors
.BuildError
:
977 self
.fail("did not catch expected BuildError")
979 # On a generic (non-BuildError) exception from a Builder,
980 # the target should throw a BuildError exception with the
981 # args set to the exception value, instance, and traceback.
982 def raise_OtherError():
986 n4
.build
= raise_OtherError
987 tm
= SCons
.Taskmaster
.Taskmaster([n4
])
991 except SCons
.Errors
.BuildError
as e
:
992 assert e
.node
== n4
, e
.node
993 assert e
.errstr
== "OtherError : ", e
.errstr
994 assert len(e
.exc_info
) == 3, e
.exc_info
995 exc_traceback
= sys
.exc_info()[2]
996 assert isinstance(e
.exc_info
[2], type(exc_traceback
)), e
.exc_info
[2]
998 self
.fail("did not catch expected BuildError")
1005 tm
= SCons
.Taskmaster
.Taskmaster([n5
])
1007 # This next line is moderately bogus. We're just reaching
1008 # in and setting the targets for this task to an array. The
1009 # "right" way to do this would be to have the next_task() call
1010 # set it up by having something that approximates a real Builder
1011 # return this list--but that's more work than is probably
1012 # warranted right now.
1013 t
.targets
= [n5
, n6
]
1015 assert built_text
== "n5 built", built_text
1016 assert cache_text
== [], cache_text
1024 tm
= SCons
.Taskmaster
.Taskmaster([n7
])
1026 # This next line is moderately bogus. We're just reaching
1027 # in and setting the targets for this task to an array. The
1028 # "right" way to do this would be to have the next_task() call
1029 # set it up by having something that approximates a real Builder
1030 # return this list--but that's more work than is probably
1031 # warranted right now.
1032 t
.targets
= [n7
, n8
]
1034 assert built_text
is None, built_text
1035 assert cache_text
== ["n7 retrieved", "n8 retrieved"], cache_text
1037 def test_cached_execute(self
) -> None:
1038 """Test executing a task with cached targets
1040 # In issue #2720 Alexei Klimkin detected that the previous
1041 # workflow for execute() led to problems in a multithreaded build.
1047 # for the Serial flow, but
1048 # - Parallel - - Worker -
1050 # requestQueue.put(task)
1051 # task = requestQueue.get()
1053 # resultQueue.put(task)
1054 # task = resultQueue.get()
1057 # in parallel. Since execute() used to call built() when a target
1058 # was cached, it could unblock dependent nodes before the binfo got
1059 # restored again in visited(). This resulted in spurious
1060 # "file not found" build errors, because files fetched from cache would
1061 # be seen as not up to date and wouldn't be scanned for implicit
1064 # The following test ensures that execute() only marks targets as cached,
1065 # but the actual call to built() happens in executed() only.
1066 # Like this, the binfo should still be intact after calling execute()...
1070 # Mark the node as being cached
1072 tm
= SCons
.Taskmaster
.Taskmaster([n1
])
1076 assert cache_text
== ["n1 retrieved"], cache_text
1077 # If no binfo exists anymore, something has gone wrong...
1078 has_binfo
= hasattr(n1
, 'binfo')
1079 assert has_binfo
, has_binfo
1081 def test_cached_execute_target_unlink_fails(self
) -> None:
1082 """Test executing a task with cached targets where unlinking one of the targets fail
1085 import SCons
.Warnings
1089 n2
= Node("not-cached")
1092 def unlink(self
, _
):
1097 # Mark the node as being cached
1099 # Add n2 as a target for n1
1100 n1
.targets
.append(n2
)
1101 # Explicitly mark n2 as not cached
1104 # Save SCons.Warnings.warn so we can mock it and catch it being called for unlink failures
1105 _save_warn
= SCons
.Warnings
.warn
1106 issued_warnings
= []
1108 def fake_warnings_warn(clz
, message
) -> None:
1109 nonlocal issued_warnings
1110 issued_warnings
.append((clz
, message
))
1111 SCons
.Warnings
.warn
= fake_warnings_warn
1113 tm
= SCons
.Taskmaster
.Taskmaster([n1
, n2
])
1118 # Restore saved warn
1119 SCons
.Warnings
.warn
= _save_warn
1121 self
.assertTrue(len(issued_warnings
) == 1,
1122 msg
='More than expected warnings (1) were issued %d' % len(issued_warnings
))
1123 self
.assertEqual(issued_warnings
[0][0], SCons
.Warnings
.CacheCleanupErrorWarning
,
1124 msg
='Incorrect warning class')
1125 self
.assertEqual(issued_warnings
[0][1],
1126 'Failed copying all target files from cache, Error while attempting to remove file n1 retrieved from cache: ')
1127 self
.assertEqual(cache_text
, ["n1 retrieved"], msg
=cache_text
)
1130 def test_exception(self
) -> None:
1131 """Test generic Taskmaster exception handling
1135 tm
= SCons
.Taskmaster
.Taskmaster([n1
])
1138 t
.exception_set((1, 2))
1139 exc_type
, exc_value
= t
.exception
1140 assert exc_type
== 1, exc_type
1141 assert exc_value
== 2, exc_value
1144 assert t
.exception
== 3
1150 t
.exception_set(None)
1153 # Having this here works for python 2.x,
1154 # but it is a tuple (None, None, None) when called outside
1155 # an except statement
1156 # t.exception_set(None)
1158 exc_type
, exc_value
, exc_tb
= t
.exception
1159 assert exc_type
is ZeroDivisionError, "Expecting ZeroDevisionError got:%s" % exc_type
1160 exception_values
= [
1161 "integer division or modulo",
1162 "integer division or modulo by zero",
1163 "integer division by zero", # PyPy2
1165 assert str(exc_value
) in exception_values
, exc_value
1167 class Exception1(Exception):
1170 # Previously value was None, but while PY2 None = "", in Py3 None != "", so set to ""
1171 t
.exception_set((Exception1
, ""))
1175 exc_type
, exc_value
= sys
.exc_info()[:2]
1176 assert exc_type
== Exception1
, exc_type
1177 assert str(exc_value
) == '', "Expecting empty string got:%s (type %s)" % (exc_value
, type(exc_value
))
1179 assert 0, "did not catch expected exception"
1181 class Exception2(Exception):
1184 t
.exception_set((Exception2
, "xyzzy"))
1188 exc_type
, exc_value
= sys
.exc_info()[:2]
1189 assert exc_type
== Exception2
, exc_type
1190 assert str(exc_value
) == "xyzzy", exc_value
1192 assert 0, "did not catch expected exception"
1194 class Exception3(Exception):
1200 tb
= sys
.exc_info()[2]
1201 t
.exception_set((Exception3
, "arg", tb
))
1205 exc_type
, exc_value
, exc_tb
= sys
.exc_info()
1206 assert exc_type
== Exception3
, exc_type
1207 assert str(exc_value
) == "arg", exc_value
1209 x
= traceback
.extract_tb(tb
)[-1]
1210 y
= traceback
.extract_tb(exc_tb
)[-1]
1211 assert x
== y
, "x = %s, y = %s" % (x
, y
)
1213 assert 0, "did not catch expected exception"
1215 def test_postprocess(self
) -> None:
1216 """Test postprocessing targets to give them a chance to clean up
1219 tm
= SCons
.Taskmaster
.Taskmaster([n1
])
1222 assert not n1
.postprocessed
1224 assert n1
.postprocessed
1228 tm
= SCons
.Taskmaster
.Taskmaster([n2
, n3
])
1230 assert not n2
.postprocessed
1231 assert not n3
.postprocessed
1234 assert n2
.postprocessed
1235 assert not n3
.postprocessed
1238 assert n2
.postprocessed
1239 assert n3
.postprocessed
1241 def test_trace(self
) -> None:
1242 """Test Taskmaster tracing
1246 trace
= io
.StringIO()
1249 n3
= Node("n3", [n1
, n2
])
1250 tm
= SCons
.Taskmaster
.Taskmaster([n1
, n1
, n3
], trace
=trace
)
1255 n1
.set_state(SCons
.Node
.executed
)
1260 n2
.set_state(SCons
.Node
.executed
)
1268 value
= trace
.getvalue()
1271 Taskmaster: Looking for a node to evaluate
1272 Taskmaster: Considering node <no_state 0 'n1'> and its children:
1273 Taskmaster: Evaluating <pending 0 'n1'>
1275 Task.make_ready_current(): node <pending 0 'n1'>
1276 Task.prepare(): node <executing 0 'n1'>
1277 Task.execute(): node <executing 0 'n1'>
1278 Task.postprocess(): node <executing 0 'n1'>
1280 Taskmaster: Looking for a node to evaluate
1281 Taskmaster: Considering node <executed 0 'n1'> and its children:
1282 Taskmaster: already handled (executed)
1283 Taskmaster: Considering node <no_state 0 'n3'> and its children:
1284 Taskmaster: <executed 0 'n1'>
1285 Taskmaster: <no_state 0 'n2'>
1286 Taskmaster: adjusted ref count: <pending 1 'n3'>, child 'n2'
1287 Taskmaster: Considering node <no_state 0 'n2'> and its children:
1288 Taskmaster: Evaluating <pending 0 'n2'>
1290 Task.make_ready_current(): node <pending 0 'n2'>
1291 Task.prepare(): node <executing 0 'n2'>
1292 Task.execute(): node <executing 0 'n2'>
1293 Task.postprocess(): node <executing 0 'n2'>
1294 Task.postprocess(): removing <executing 0 'n2'>
1295 Task.postprocess(): adjusted parent ref count <pending 0 'n3'>
1297 Taskmaster: Looking for a node to evaluate
1298 Taskmaster: Considering node <pending 0 'n3'> and its children:
1299 Taskmaster: <executed 0 'n1'>
1300 Taskmaster: <executed 0 'n2'>
1301 Taskmaster: Evaluating <pending 0 'n3'>
1303 Task.make_ready_current(): node <pending 0 'n3'>
1304 Task.prepare(): node <executing 0 'n3'>
1305 Task.execute(): node <executing 0 'n3'>
1306 Task.postprocess(): node <executing 0 'n3'>
1308 Taskmaster: Looking for a node to evaluate
1309 Taskmaster: No candidate anymore.
1313 TestCommon
.TestCommon
.detailed_diff(value
, expect
)
1315 assert value
== expect
, "Expected taskmaster trace contents didn't match. See above"
1318 if __name__
== "__main__":
1323 # indent-tabs-mode:nil
1325 # vim: set expandtab tabstop=4 shiftwidth=4: