153 lines
4.3 KiB
Go
153 lines
4.3 KiB
Go
package core_test
|
|
|
|
import (
|
|
"sort"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/microsoft/typescript-go/internal/collections"
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"gotest.tools/v3/assert"
|
|
)
|
|
|
|
func TestBreadthFirstSearchParallel(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("basic functionality", func(t *testing.T) {
|
|
t.Parallel()
|
|
// Test basic functionality with a simple DAG
|
|
// Graph: A -> B, A -> C, B -> D, C -> D
|
|
graph := map[string][]string{
|
|
"A": {"B", "C"},
|
|
"B": {"D"},
|
|
"C": {"D"},
|
|
"D": {},
|
|
}
|
|
|
|
children := func(node string) []string {
|
|
return graph[node]
|
|
}
|
|
|
|
t.Run("find specific node", func(t *testing.T) {
|
|
t.Parallel()
|
|
result := core.BreadthFirstSearchParallel("A", children, func(node string) (bool, bool) {
|
|
return node == "D", true
|
|
})
|
|
assert.Equal(t, result.Stopped, true, "Expected search to stop at D")
|
|
assert.DeepEqual(t, result.Path, []string{"D", "B", "A"})
|
|
})
|
|
|
|
t.Run("visit all nodes", func(t *testing.T) {
|
|
t.Parallel()
|
|
var mu sync.Mutex
|
|
var visitedNodes []string
|
|
result := core.BreadthFirstSearchParallel("A", children, func(node string) (bool, bool) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
visitedNodes = append(visitedNodes, node)
|
|
return false, false // Never stop early
|
|
})
|
|
|
|
// Should return nil since we never return true
|
|
assert.Equal(t, result.Stopped, false, "Expected search to not stop early")
|
|
assert.Assert(t, result.Path == nil, "Expected nil path when visit function never returns true")
|
|
|
|
// Should visit all nodes exactly once
|
|
sort.Strings(visitedNodes)
|
|
expected := []string{"A", "B", "C", "D"}
|
|
assert.DeepEqual(t, visitedNodes, expected)
|
|
})
|
|
})
|
|
|
|
t.Run("early termination", func(t *testing.T) {
|
|
t.Parallel()
|
|
// Test that nodes below the target level are not visited
|
|
graph := map[string][]string{
|
|
"Root": {"L1A", "L1B"},
|
|
"L1A": {"L2A", "L2B"},
|
|
"L1B": {"L2C"},
|
|
"L2A": {"L3A"},
|
|
"L2B": {},
|
|
"L2C": {},
|
|
"L3A": {},
|
|
}
|
|
|
|
children := func(node string) []string {
|
|
return graph[node]
|
|
}
|
|
|
|
var visited collections.SyncSet[string]
|
|
core.BreadthFirstSearchParallelEx("Root", children, func(node string) (bool, bool) {
|
|
return node == "L2B", true // Stop at level 2
|
|
}, core.BreadthFirstSearchOptions[string, string]{
|
|
Visited: &visited,
|
|
},
|
|
core.Identity)
|
|
|
|
assert.Assert(t, visited.Has("Root"), "Expected to visit Root")
|
|
assert.Assert(t, visited.Has("L1A"), "Expected to visit L1A")
|
|
assert.Assert(t, visited.Has("L1B"), "Expected to visit L1B")
|
|
assert.Assert(t, visited.Has("L2A"), "Expected to visit L2A")
|
|
assert.Assert(t, visited.Has("L2B"), "Expected to visit L2B")
|
|
// L2C is non-deterministic
|
|
assert.Assert(t, !visited.Has("L3A"), "Expected not to visit L3A")
|
|
})
|
|
|
|
t.Run("returns fallback when no other result found", func(t *testing.T) {
|
|
t.Parallel()
|
|
// Test that fallback behavior works correctly
|
|
graph := map[string][]string{
|
|
"A": {"B", "C"},
|
|
"B": {"D"},
|
|
"C": {"D"},
|
|
"D": {},
|
|
}
|
|
|
|
children := func(node string) []string {
|
|
return graph[node]
|
|
}
|
|
|
|
var visited collections.SyncSet[string]
|
|
result := core.BreadthFirstSearchParallelEx("A", children, func(node string) (bool, bool) {
|
|
return node == "A", false // Record A as a fallback, but do not stop
|
|
}, core.BreadthFirstSearchOptions[string, string]{
|
|
Visited: &visited,
|
|
},
|
|
core.Identity)
|
|
|
|
assert.Equal(t, result.Stopped, false, "Expected search to not stop early")
|
|
assert.DeepEqual(t, result.Path, []string{"A"})
|
|
assert.Assert(t, visited.Has("B"), "Expected to visit B")
|
|
assert.Assert(t, visited.Has("C"), "Expected to visit C")
|
|
assert.Assert(t, visited.Has("D"), "Expected to visit D")
|
|
})
|
|
|
|
t.Run("returns a stop result over a fallback", func(t *testing.T) {
|
|
t.Parallel()
|
|
// Test that a stop result is preferred over a fallback
|
|
graph := map[string][]string{
|
|
"A": {"B", "C"},
|
|
"B": {"D"},
|
|
"C": {"D"},
|
|
"D": {},
|
|
}
|
|
|
|
children := func(node string) []string {
|
|
return graph[node]
|
|
}
|
|
|
|
result := core.BreadthFirstSearchParallel("A", children, func(node string) (bool, bool) {
|
|
switch node {
|
|
case "A":
|
|
return true, false // Record fallback
|
|
case "D":
|
|
return true, true // Stop at D
|
|
default:
|
|
return false, false
|
|
}
|
|
})
|
|
|
|
assert.Equal(t, result.Stopped, true, "Expected search to stop at D")
|
|
assert.DeepEqual(t, result.Path, []string{"D", "B", "A"})
|
|
})
|
|
}
|