Skip to content

Commit

Permalink
Circular dependency check
Browse files Browse the repository at this point in the history
  • Loading branch information
F1bonacc1 committed Jul 20, 2023
1 parent 9fb6eaa commit 08ddae9
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 33 deletions.
13 changes: 4 additions & 9 deletions fixtures-code/process-compose-circular.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@ processes:

process2:
command: "echo process2"
availability:
restart: "on_failure"
depends_on:
process3:
process1:
condition: process_completed_successfully

process3:
command: "echo process3"
availability:
restart: "on_failure"
backoff_seconds: 2
# depends_on:
# process1:
# condition: process_completed_successfully
depends_on:
process1:
condition: process_completed_successfully

11 changes: 11 additions & 0 deletions fixtures-code/process-compose-non-circular.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: "0.5"
processes:
process1:
command: "echo process1"

process3:
command: "echo process3"
depends_on:
process1:
condition: process_completed_successfully

43 changes: 21 additions & 22 deletions src/app/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,28 +130,27 @@ func TestSystem_TestComposeChainExit(t *testing.T) {
})
}

//func TestSystem_TestComposeCircular(t *testing.T) {
// fixture := filepath.Join("..", "..", "fixtures", "process-compose-circular.yaml")
// t.Run(fixture, func(t *testing.T) {
// project, err := loader.Load(&loader.LoaderOptions{
// FileNames: []string{fixture},
// })
// if err != nil {
// t.Errorf(err.Error())
// return
// }
// runner, err := NewProjectRunner(project, []string{}, false)
// if err != nil {
// t.Errorf(err.Error())
// return
// }
// exitCode := runner.Run()
// want := 42
// if want != exitCode {
// t.Errorf("Project.Run() = %v, want %v", exitCode, want)
// }
// })
//}
func TestSystem_TestComposeCircular(t *testing.T) {
fixture1 := filepath.Join("..", "..", "fixtures-code", "process-compose-circular.yaml")
fixture2 := filepath.Join("..", "..", "fixtures-code", "process-compose-non-circular.yaml")
t.Run(fixture1, func(t *testing.T) {
_, err := loader.Load(&loader.LoaderOptions{
FileNames: []string{fixture1},
})
if err == nil {
t.Errorf("should fail on cirlcular dependency")
return
}

_, err = loader.Load(&loader.LoaderOptions{
FileNames: []string{fixture2},
})
if err != nil {
t.Errorf(err.Error())
return
}
})
}

func TestSystem_TestComposeScale(t *testing.T) {
fixture := filepath.Join("..", "..", "fixtures-code", "process-compose-scale.yaml")
Expand Down
2 changes: 1 addition & 1 deletion src/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func Load(opts *LoaderOptions) (*types.Project, error) {
opts.projects = append(opts.projects, p)
}
mergedProject, err := merge(opts)
mergedProject.ValidateAfterMerge()
err = mergedProject.ValidateAfterMerge()
return mergedProject, err

}
Expand Down
42 changes: 41 additions & 1 deletion src/types/validators.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"fmt"
"github.com/f1bonacc1/process-compose/src/command"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
Expand All @@ -14,9 +15,10 @@ func (p *Project) Validate() {
p.validateProcessConfig()
}

func (p *Project) ValidateAfterMerge() {
func (p *Project) ValidateAfterMerge() error {
p.assignDefaultProcessValues()
p.cloneReplicas()
return p.validateNoCircularDependencies()
}

func (p *Project) validateLogLevel() {
Expand Down Expand Up @@ -100,3 +102,41 @@ func (p *Project) cloneReplicas() {
p.Processes[proc.ReplicaName] = proc
}
}

func (p *Project) validateNoCircularDependencies() error {
visited := make(map[string]bool, len(p.Processes))
stack := make(map[string]bool)
for name := range p.Processes {
if !visited[name] {
if p.isCyclicHelper(name, visited, stack) {
return fmt.Errorf("circular dependency found in %s", name)
}
}
}
return nil
}

func (p *Project) isCyclicHelper(procName string, visited map[string]bool, stack map[string]bool) bool {
visited[procName] = true
stack[procName] = true

processes, err := p.getProcesses(procName)
if err != nil {
return false
}
for _, process := range processes {
dependencies := process.GetDependencies()
for _, neighbor := range dependencies {
if !visited[neighbor] {
if p.isCyclicHelper(neighbor, visited, stack) {
return true
}
} else if stack[neighbor] {
return true
}
}
}

stack[procName] = false
return false
}

0 comments on commit 08ddae9

Please sign in to comment.