note description: "[ Objects representing an exception raised while invoking a test class and/or feature. Note: EQA_TEST_INVOCATION_EXCEPTION does not only contain the exception information, it also analyses the stack trace to determine whether the implementation or the test code is to blame. TODO: Take stack frames of agent calls into account, meaning that even though {PROCEDURE}.fast_call causes a precondition violation, the stack frame calling the agent is to blame. ]" date: "$Date: 2017-05-03 14:51:35 +0000 (Wed, 03 May 2017) $" revision: "$Revision: 100315 $" class EQA_TEST_INVOCATION_EXCEPTION inherit ANY EXCEP_CONST export {NONE} all end INTERNAL rename class_name as class_name_from_object, dynamic_type as dynamic_type_from_object export {NONE} all end MISMATCH_CORRECTOR redefine correct_mismatch end create make feature {NONE} -- Initialization make (a_exception: EXCEPTION; a_class_name, a_feature_name: detachable READABLE_STRING_8) -- Initialize Current. -- -- Note: If a_test_class_name is attached, the stack trace will be truncated after the last -- frame containing a call to the class (if a_test_feature_name the feature name of the -- stack frame is also considered). In addition it will be determined, whether the class -- a_class_name (or feature a_feature_name if provided) is to blame, or any routine -- further down the call stack. -- If a_test_class_name is Void, the complete trace will be made available in trace. -- -- a_exception: Original exception object. -- a_class_name: Name of test class in which exception was raised, or Void. -- a_feature_name: Name of test feature in which exception was raised, or Void. require a_exception_attached: a_exception /= Void a_feature_name_attached_implies_a_class_name_attached: a_feature_name /= Void implies a_class_name /= Void a_feature_name_not_empty: a_feature_name /= Void implies not a_feature_name.is_empty do code := a_exception.code if attached a_exception.type_name as l_type then class_name := l_type.string else create {STRING_8} class_name.make_empty end if attached a_exception.recipient_name as l_rec then recipient_name := l_rec.string else create {STRING_8} recipient_name.make_empty end if attached a_exception.description as l_tag then create {IMMUTABLE_STRING_32} tag_name.make_from_string_general (l_tag) else create {IMMUTABLE_STRING_32} tag_name.make_empty end if attached a_exception.trace as l_trace and then not l_trace.is_empty then parse_trace (l_trace, a_class_name, a_feature_name) else create {IMMUTABLE_STRING_32} trace.make_empty end end parse_trace (a_trace: like trace; a_class_name, a_feature_name: detachable READABLE_STRING_8) -- Initialize trace from original trace by cutting off routines which invoked testing routine. -- -- a_trace: Original exception trace. -- a_class_name: Name of test class in which exception was raised, or Void. -- a_feature_name: Name of test feature in which exception was raised, or Void. require a_trace_not_empty: a_trace /= Void and then not a_trace.is_empty a_feature_name_attached_implies_a_class_name_attached: a_feature_name /= Void implies a_class_name /= Void a_feature_name_not_empty: a_feature_name /= Void implies not a_feature_name.is_empty code_valid: valid_code (code) local i, l_last, l_depth: INTEGER_32 l_prev, l_found, l_bp_set: BOOLEAN do i := go_after_next_dash_line (a_trace, 1) i := go_after_next_dash_line (a_trace, i) if a_class_name /= Void then from until i = 0 loop if not l_found then l_depth := l_depth + 1 end parse_frame (a_trace, i) if not l_bp_set then break_point_slot := last_break_point_slot l_bp_set := True end if attached last_class_name as l_cn then if l_cn.same_string_general (a_class_name) and (a_feature_name /= Void implies (attached last_routine_name as l_rn and then l_rn.same_string_general (a_feature_name))) then l_found := True l_prev := True elseif l_prev then l_last := i - 1 l_prev := False end i := go_after_next_dash_line (a_trace, i) else i := 0 end end if l_found then if code = Precondition then is_test_invalid := l_depth <= 2 else is_test_invalid := l_depth <= 1 end if l_prev then l_last := a_trace.count end is_trace_valid := True else is_test_invalid := True l_last := a_trace.count end else parse_frame (a_trace, i) break_point_slot := last_break_point_slot is_trace_valid := i > 0 l_last := a_trace.count end trace := a_trace.substring (1, l_last) end feature -- Access code: INTEGER_32 -- Exception code recipient_name: READABLE_STRING_8 -- Name of the feature that triggered the exception class_name: READABLE_STRING_8 -- Name of the class in which the exception occurred tag_name: READABLE_STRING_32 -- Tag describing the exception trace: READABLE_STRING_32 -- Text based representation of the stack trace break_point_slot: INTEGER_32 -- Break point slot in exception recipient that triggered exception; -- Note that the number of slots available in a routine may change depending -- on the level of assertion monitoring. feature {NONE} -- Access: parsing last_class_name: detachable like trace -- Class name last parsed through parse_frame last_routine_name: detachable like trace -- Routine name last parsed through parse_frame last_break_point_slot: like break_point_slot -- Last breakpoint slot parsed by is_trace_valid, zero if slot information could not be found. feature -- Status report is_trace_valid: BOOLEAN -- Does trace have an expected format? -- -- Note: is provided class/feature name in creation procedure was not found in exception trace, -- trace will contain the original exception trace and is_trace_valid will be False. is_test_invalid: BOOLEAN -- Does this exception show that the associated test is invalid. -- An invalid test happens when the system is not brought into a state -- suitable to test the feature under test. For example, if the precondition -- of the feature under test is not satisfied. feature {NONE} -- Implementation go_after_next_dash_line (a_trace: like trace; a_start: INTEGER_32): INTEGER_32 -- Index of character immediatly after next occurance of Dash_line. -- -- Note: If a_start is 0, Result will automatically be 0 as well. This allows -- go_after_next_dash_line to be called iteratively without checking the result. -- -- a_trace: Trace containing stack frames. -- a_start: Start position from which next stack frame will be located. -- Result: Contains index of first character of next stack frame, or 0 if no frame found. require a_start_valid: a_start >= 0 and a_start <= a_trace.count local i, l_count: INTEGER_32 do if a_start > 0 then l_count := Dash_line.count if Dash_line.has (a_trace.item (a_start)) then i := (a_start - l_count - 1).max (1) else i := a_start end i := a_trace.substring_index (Dash_line, i) if i > 0 then Result := i + l_count if Result > a_trace.count then Result := 0 end end end ensure result_valid: Result = 0 or else (Result > a_start and then Result <= a_trace.count) start_zero_implies_result_zero: a_start = 0 implies Result = 0 end parse_frame (a_trace: like trace; a_position: INTEGER_32) -- Parse current frame in stack trace and set last_class, last_routine and -- last_break_point_slot accordingly. -- -- a_trace: Complete stack trace. -- a_position: Position in a_trace where current frame begins. require a_trace_attached: a_trace /= Void a_position_valid: a_position > 0 and a_position <= a_trace.count local i, j, l_count, l_bp: INTEGER_32 l_done: BOOLEAN l_substring: like trace c: CHARACTER_32 do last_class_name := Void last_routine_name := Void last_break_point_slot := 0 from j := a_position l_count := a_trace.count until l_done loop from i := j until j > l_count or else (a_trace.item (j).is_space and i < j) loop c := a_trace.item (j) j := j + 1 if c.is_space or c = '@'.to_character_32 then i := j end end if i < j then l_substring := a_trace.substring (i, j - 1) if last_class_name = Void then last_class_name := l_substring elseif last_routine_name = Void then last_routine_name := l_substring else if l_substring.is_integer then l_bp := l_substring.to_integer if l_bp > 0 then last_break_point_slot := l_bp end end l_done := True end else l_done := True end end end feature {NONE} -- Constants Dash_line: STRING_32 = "-------------------------------------------------------------------------------%N" Class_attribute_name: STRING_8 = "class_name" Recipient_attribute_name: STRING_8 = "internal_exception" Tag_attribute_name: STRING_8 = "tag_name" Trace_attribute_name: STRING_8 = "trace" -- Name of attributes in Current feature -- Mismatch Correnction correct_mismatch -- Attempt to correct object mismatch using Mismatch_information. do if attached {like class_name} Mismatch_information.item (Class_attribute_name) as l_class and attached {like recipient_name} Mismatch_information.item (Recipient_attribute_name) as l_recipient and attached {like tag_name} Mismatch_information.item (Tag_attribute_name) as l_tag and attached {like trace} Mismatch_information.item (Trace_attribute_name) as l_trace then create {STRING_8} class_name.make_from_string (l_class) create {STRING_8} recipient_name.make_from_string (l_recipient) create {IMMUTABLE_STRING_32} tag_name.make_from_string (l_tag) create {IMMUTABLE_STRING_32} trace.make_from_string (l_trace) else Precursor end end invariant code_valid: valid_code (code) recipient_name_attached: recipient_name /= Void class_name_attached: class_name /= Void tag_attached: tag_name /= Void trace_attached: trace /= Void exception_break_point_slot_positive: break_point_slot >= 0 note copyright: "Copyright (c) 1984-2017, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software 5949 Hollister Ave., Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end -- class EQA_TEST_INVOCATION_EXCEPTION
Generated by ISE EiffelStudio