Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions documentation/w3c_test_improvement_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# W3C Test Improvement Plan

## Current Status

**Test Pass Rate:** 22/59 W3C tests passing (37.3%)
**Recent Improvement:** Fixed multi-state initial configuration parsing - test576 and test413 now pass
**Baseline:** Up from 20/59 (34%) before the multi-state initial fix

## High-Impact Quick Wins (Estimated 2-4 weeks)

### 1. Enhanced Data Model Support

**Impact:** ~8-12 additional tests
**Effort:** Medium

- **Missing Features:**
- `<datamodel>` / `<data>` element initialization
- Enhanced variable storage and access patterns
- JavaScript-style expression evaluation improvements

**Target Tests:** test277, test276sub1, test550, test551 (data manipulation tests)

### 2. Improved Event Processing

**Impact:** ~6-8 additional tests
**Effort:** Medium

- **Missing Features:**
- Enhanced event queuing semantics
- Proper event data handling and propagation
- Cross-state event communication improvements

**Target Tests:** test399, test401, test402 (event processing tests)

### 3. Advanced State Machine Features

**Impact:** ~4-6 additional tests
**Effort:** Medium-High

- **Missing Features:**
- Targetless transitions (internal transitions)
- Enhanced transition conflict resolution
- Improved parallel state semantics

**Target Tests:** test406, test412, test416, test419, test423 (transition selection tests)

## Medium-Term Improvements (4-8 weeks)

### 4. Complete Executable Content

**Impact:** ~8-10 additional tests
**Effort:** High

- **Missing Features:**
- `<send>` elements with delay support
- `<script>` element execution
- Advanced `<foreach>` iteration constructs

**Target Tests:** test155, test156, test525 (foreach tests), various send/script tests

### 5. Advanced History State Features

**Impact:** ~3-4 additional tests
**Effort:** Medium

- **Missing Features:**
- Complex history restoration scenarios
- History state default transition improvements
- Deep vs shallow history edge cases

**Target Tests:** test388, test579, test580 (advanced history tests)

### 6. Final State Handling

**Impact:** ~2-3 additional tests
**Effort:** Low-Medium

- **Missing Features:**
- Enhanced final state semantics
- Proper final state event generation
- Final state hierarchy handling

**Target Tests:** test570 (final state tests)

## Implementation Strategy

### Phase 1: Data Model Enhancement (Priority 1)

1. **Parser Updates:** Enhance `<data>` element parsing with expression evaluation
2. **Runtime Storage:** Improve datamodel variable storage and initialization
3. **Expression Engine:** Integrate enhanced JavaScript-style expression evaluation
4. **Test Integration:** Update test infrastructure to handle data-driven scenarios

### Phase 2: Event Processing Improvements (Priority 2)

1. **Event Queue Semantics:** Implement proper internal/external event queuing per SCXML spec
2. **Event Data Propagation:** Ensure event data is properly passed and accessible
3. **Cross-State Communication:** Improve event handling between parallel regions
4. **Validation Updates:** Enhance event-related validation rules

### Phase 3: Advanced State Machine Features (Priority 3)

1. **Targetless Transitions:** Implement internal transitions without target states
2. **Transition Conflict Resolution:** Enhance SCXML-compliant transition selection
3. **Parallel State Semantics:** Improve concurrent execution and exit handling
4. **Optimization:** Maintain O(1) lookup performance for advanced features

## Success Metrics

- **Phase 1 Target:** 30+ tests passing (51% pass rate)
- **Phase 2 Target:** 36+ tests passing (61% pass rate)
- **Phase 3 Target:** 42+ tests passing (71% pass rate)
- **Ultimate Goal:** 50+ tests passing (85% pass rate)

## Technical Approach

### Maintain Architecture Principles

- **Parse → Validate → Optimize** workflow
- **O(1) lookup optimizations** for performance
- **Comprehensive test coverage** for regressions
- **SCXML specification compliance** over custom extensions

### Development Workflow

1. **Analyze failing tests** to identify specific missing features
2. **Implement core functionality** with proper validation
3. **Update test expectations** and fix any regressions
4. **Run full test suite** to ensure no breaking changes
5. **Measure improvement** against W3C test pass rate

## Current Blockers Analysis

### Most Common Test Failure Patterns

1. **Data model operations** - Missing variable initialization and manipulation
2. **Event handling** - Incomplete event queuing and processing semantics
3. **Transition selection** - Advanced SCXML transition conflict resolution
4. **Executable content** - Missing `<send>`, `<script>`, and `<foreach>` support

### Technical Debt to Address

- Some tests create invalid document structures (fixed in multi-state work)
- Expression evaluation needs JavaScript compatibility improvements
- Event queuing semantics need W3C SCXML compliance review

## Next Steps

1. **Start with data model enhancement** - highest impact, medium effort
2. **Create feature branch** for data model work
3. **Implement `<data>` element initialization** with expression evaluation
4. **Update failing data-related tests** (test277, test276sub1, etc.)
5. **Measure improvement** and proceed to event processing phase

---

*Last Updated: 2025-09-07*
*Status: 22/59 tests passing (37.3%) - recent multi-state initial configuration fix complete*
4 changes: 2 additions & 2 deletions lib/statifier/document.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ defmodule Statifier.Document do

defstruct [
:name,
:initial,
:datamodel,
:version,
:xmlns,
initial: [],
states: [],
datamodel_elements: [],
# Performance optimization: O(1) lookups
Expand All @@ -30,10 +30,10 @@ defmodule Statifier.Document do

@type t :: %__MODULE__{
name: String.t() | nil,
initial: String.t() | nil,
datamodel: String.t() | nil,
version: String.t() | nil,
xmlns: String.t() | nil,
initial: [String.t()],
states: [Statifier.State.t()],
datamodel_elements: [Statifier.Data.t()],
# Lookup maps for O(1) access
Expand Down
63 changes: 38 additions & 25 deletions lib/statifier/interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ defmodule Statifier.Interpreter do
end
end

defp get_initial_configuration(%Document{initial: [], states: []}), do: %Configuration{}

defp get_initial_configuration(%Document{initial: nil, states: []}), do: %Configuration{}

defp get_initial_configuration(
Expand All @@ -249,16 +251,21 @@ defmodule Statifier.Interpreter do
Configuration.new(initial_states)
end

defp get_initial_configuration(%Document{initial: initial_id} = document) do
case Document.find_state(document, initial_id) do
# Invalid initial state
nil ->
%Configuration{}
defp get_initial_configuration(%Document{initial: [], states: [first_state | _rest]} = document) do
initial_states = enter_state(first_state, document)
Configuration.new(initial_states)
end

state ->
initial_states = enter_state(state, document)
Configuration.new(initial_states)
end
defp get_initial_configuration(%Document{initial: initial_ids} = document)
when length(initial_ids) > 0 do
initial_states =
initial_ids
|> Enum.map(&Document.find_state(document, &1))
# Remove any invalid states
|> Enum.filter(&(&1 != nil))
|> Enum.flat_map(&enter_state(&1, document))

Configuration.new(initial_states)
end

defp enter_state(%State{} = state, %Document{} = document),
Expand All @@ -283,17 +290,14 @@ defmodule Statifier.Interpreter do
end

defp enter_state(
%State{type: :compound, states: child_states, initial: initial_id},
%State{type: :compound, states: child_states, initial: initial_ids},
%StateChart{} = state_chart
) do
# Compound state - find and enter initial child (don't add compound state to active set)
initial_child = get_initial_child_state(initial_id, child_states)
# Compound state - find and enter initial children (don't add compound state to active set)
initial_children = get_initial_child_states(initial_ids, child_states)

case initial_child do
# No valid child - compound state with no children is not active
nil -> []
child -> enter_state(child, state_chart)
end
initial_children
|> Enum.flat_map(&enter_state(&1, state_chart))
end

defp enter_state(
Expand Down Expand Up @@ -330,8 +334,23 @@ defmodule Statifier.Interpreter do
end
end

# Get the initial child state for a compound state
defp get_initial_child_state(nil, child_states) do
# Get the initial child states for a compound state (handles multiple initial states)
defp get_initial_child_states([], child_states) do
# No initial attribute - check for <initial> element first or use first child
case get_initial_child_state_legacy([], child_states) do
nil -> []
child -> [child]
end
end

defp get_initial_child_states(initial_ids, child_states) when is_list(initial_ids) do
initial_ids
|> Enum.map(&find_child_by_id(child_states, &1))
|> Enum.filter(&(&1 != nil))
end

# Legacy function for backward compatibility (single child)
defp get_initial_child_state_legacy([], child_states) do
# No initial attribute - check for <initial> element first
case find_initial_element(child_states) do
%State{type: :initial, transitions: [transition | _rest]} ->
Expand All @@ -357,12 +376,6 @@ defmodule Statifier.Interpreter do
end
end

defp get_initial_child_state(initial_id, child_states) when is_binary(initial_id) do
Enum.find(child_states, &(&1.id == initial_id))
end

defp get_initial_child_state(_initial_id, []), do: nil

# Find the initial element among child states
defp find_initial_element(child_states) do
Enum.find(child_states, &(&1.type == :initial))
Expand Down
26 changes: 20 additions & 6 deletions lib/statifier/parser/scxml/element_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule Statifier.Parser.SCXML.ElementBuilder do

%Statifier.Document{
name: get_attr_value(attrs_map, "name"),
initial: get_attr_value(attrs_map, "initial"),
initial: parse_initial_attribute(get_attr_value(attrs_map, "initial")),
datamodel: get_attr_value(attrs_map, "datamodel"),
version: get_attr_value(attrs_map, "version"),
xmlns: get_attr_value(attrs_map, "xmlns"),
Expand Down Expand Up @@ -73,7 +73,7 @@ defmodule Statifier.Parser.SCXML.ElementBuilder do

%Statifier.State{
id: get_attr_value(attrs_map, "id"),
initial: get_attr_value(attrs_map, "initial"),
initial: parse_initial_attribute(get_attr_value(attrs_map, "initial")),
# Will be updated later based on children and structure
type: :atomic,
states: [],
Expand All @@ -100,7 +100,7 @@ defmodule Statifier.Parser.SCXML.ElementBuilder do
%Statifier.State{
id: get_attr_value(attrs_map, "id"),
# Parallel states don't have initial attributes
initial: nil,
initial: [],
# Set type directly during parsing
type: :parallel,
states: [],
Expand Down Expand Up @@ -128,7 +128,7 @@ defmodule Statifier.Parser.SCXML.ElementBuilder do
%Statifier.State{
id: get_attr_value(attrs_map, "id"),
# Final states don't have initial attributes
initial: nil,
initial: [],
# Set type directly during parsing
type: :final,
states: [],
Expand All @@ -155,7 +155,7 @@ defmodule Statifier.Parser.SCXML.ElementBuilder do
%Statifier.State{
# Initial states generate unique IDs since they don't have explicit IDs
id: generate_initial_id(element_counts),
initial: nil,
initial: [],
type: :initial,
states: [],
transitions: [],
Expand Down Expand Up @@ -196,7 +196,7 @@ defmodule Statifier.Parser.SCXML.ElementBuilder do

%Statifier.State{
id: get_attr_value(attrs_map, "id"),
initial: nil,
initial: [],
type: :history,
history_type: history_type,
states: [],
Expand Down Expand Up @@ -516,4 +516,18 @@ defmodule Statifier.Parser.SCXML.ElementBuilder do
initial_count = Map.get(element_counts, "initial", 1)
"__initial_#{initial_count}__"
end

# Parse the initial attribute which can be space-separated state IDs.
#
# Returns a list of state IDs. If the attribute is nil or empty, returns an empty list.
# If it contains space-separated values, splits them and returns the list.
defp parse_initial_attribute(nil), do: []
defp parse_initial_attribute(""), do: []

defp parse_initial_attribute(initial_string) when is_binary(initial_string) do
initial_string
|> String.split()
|> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == ""))
end
end
2 changes: 1 addition & 1 deletion lib/statifier/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule Statifier.State do

@type t :: %__MODULE__{
id: String.t(),
initial: String.t() | nil,
initial: [String.t()],
type: state_type(),
states: [Statifier.State.t()],
transitions: [Statifier.Transition.t()],
Expand Down
Loading