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