forked from aws/copilot-cli
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(manifest): move platform validation and container dependencies (a…
…ws#2869) <!-- Provide summary of changes --> part of aws#2818 <!-- Issue number, if available. E.g. "Fixes aws#31", "Addresses aws#42, 77" --> By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License.
- Loading branch information
1 parent
994245a
commit dc42b97
Showing
14 changed files
with
805 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Package graph provides functionality for directed graphs. | ||
package graph | ||
|
||
// nodeStatus denotes the visiting status of a node when running DFS in a graph. | ||
type nodeStatus int | ||
|
||
const ( | ||
unvisited nodeStatus = iota + 1 | ||
visiting | ||
visited | ||
) | ||
|
||
// Graph represents a directed graph. | ||
type Graph struct { | ||
nodes map[string]neighbors | ||
} | ||
|
||
// Edge represents one edge of a directed graph. | ||
type Edge struct { | ||
From string | ||
To string | ||
} | ||
|
||
type neighbors map[string]bool | ||
|
||
// New initiates a new Graph. | ||
func New() *Graph { | ||
return &Graph{ | ||
nodes: make(map[string]neighbors), | ||
} | ||
} | ||
|
||
// Add adds a connection between two Nodes. | ||
func (g *Graph) Add(edge Edge) { | ||
fromNode, toNode := edge.From, edge.To | ||
// Add origin node if doesn't exist. | ||
if _, ok := g.nodes[fromNode]; !ok { | ||
g.nodes[fromNode] = make(neighbors) | ||
} | ||
// Add edge. | ||
g.nodes[fromNode][toNode] = true | ||
} | ||
|
||
type findCycleTempVars struct { | ||
status map[string]nodeStatus | ||
nodeParent map[string]string | ||
cycleStart string | ||
cycleEnd string | ||
} | ||
|
||
// IsAcyclic checks if the graph is acyclic. If not, return the first detected cycle. | ||
func (g *Graph) IsAcyclic() ([]string, bool) { | ||
var cycle []string | ||
status := make(map[string]nodeStatus) | ||
for node := range g.nodes { | ||
status[node] = unvisited | ||
} | ||
temp := findCycleTempVars{ | ||
status: status, | ||
nodeParent: make(map[string]string), | ||
} | ||
// We will run a series of DFS in the graph. Initially all vertices are marked unvisited. | ||
// From each unvisited node, start the DFS, mark it visiting while entering and mark it visited on exit. | ||
// If DFS moves to a visiting node, then we have found a cycle. The cycle itself can be reconstructed using parent map. | ||
// See https://cp-algorithms.com/graph/finding-cycle.html | ||
for node := range g.nodes { | ||
if status[node] == unvisited && g.hasCycles(&temp, node) { | ||
for n := temp.cycleStart; n != temp.cycleEnd; n = temp.nodeParent[n] { | ||
cycle = append(cycle, n) | ||
} | ||
cycle = append(cycle, temp.cycleEnd) | ||
return cycle, false | ||
} | ||
} | ||
return nil, true | ||
} | ||
|
||
func (g *Graph) hasCycles(temp *findCycleTempVars, currNode string) bool { | ||
temp.status[currNode] = visiting | ||
for node := range g.nodes[currNode] { | ||
if temp.status[node] == unvisited { | ||
temp.nodeParent[node] = currNode | ||
if g.hasCycles(temp, node) { | ||
return true | ||
} | ||
} else if temp.status[node] == visiting { | ||
temp.cycleStart = currNode | ||
temp.cycleEnd = node | ||
return true | ||
} | ||
} | ||
temp.status[currNode] = visited | ||
return false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package graph | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestGraph_Add(t *testing.T) { | ||
t.Run("success", func(t *testing.T) { | ||
// GIVEN | ||
graph := New() | ||
|
||
// WHEN | ||
graph.Add(Edge{ | ||
From: "A", | ||
To: "B", | ||
}) | ||
graph.Add(Edge{ | ||
From: "B", | ||
To: "A", | ||
}) | ||
graph.Add(Edge{ | ||
From: "A", | ||
To: "C", | ||
}) | ||
|
||
// THEN | ||
require.Equal(t, graph.nodes["A"], neighbors{"B": true, "C": true}) | ||
require.Equal(t, graph.nodes["B"], neighbors{"A": true}) | ||
}) | ||
} | ||
|
||
func TestGraph_IsAcyclic(t *testing.T) { | ||
testCases := map[string]struct { | ||
graph Graph | ||
|
||
isAcyclic bool | ||
cycle []string | ||
}{ | ||
"small non acyclic graph": { | ||
graph: Graph{ | ||
nodes: map[string]neighbors{ | ||
"A": {"B": true, "C": true}, | ||
"B": {"A": true}, | ||
}, | ||
}, | ||
|
||
isAcyclic: false, | ||
cycle: []string{"A", "B"}, | ||
}, | ||
"non acyclic": { | ||
graph: Graph{ | ||
nodes: map[string]neighbors{ | ||
"K": {"F": true}, | ||
"A": {"B": true, "C": true}, | ||
"B": {"D": true, "E": true}, | ||
"E": {"G": true}, | ||
"F": {"G": true}, | ||
"G": {"A": true}, | ||
}, | ||
}, | ||
|
||
isAcyclic: false, | ||
cycle: []string{"A", "G", "E", "B"}, | ||
}, | ||
"acyclic": { | ||
graph: Graph{ | ||
nodes: map[string]neighbors{ | ||
"A": {"B": true, "C": true}, | ||
"B": {"D": true}, | ||
"E": {"G": true}, | ||
"F": {"G": true}, | ||
}, | ||
}, | ||
|
||
isAcyclic: true, | ||
}, | ||
} | ||
for name, tc := range testCases { | ||
t.Run(name, func(t *testing.T) { | ||
// WHEN | ||
gotCycle, gotAcyclic := tc.graph.IsAcyclic() | ||
|
||
// THEN | ||
require.Equal(t, tc.isAcyclic, gotAcyclic) | ||
require.ElementsMatch(t, tc.cycle, gotCycle) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.