Circular dependency check

This commit is contained in:
Berger Eugene 2023-07-08 15:05:39 +03:00
parent 9fb6eaa132
commit 08ddae986e
5 changed files with 78 additions and 33 deletions

View File

@ -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

View File

@ -0,0 +1,11 @@
version: "0.5"
processes:
process1:
command: "echo process1"
process3:
command: "echo process3"
depends_on:
process1:
condition: process_completed_successfully

View File

@ -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")

View File

@ -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
}

View File

@ -1,6 +1,7 @@
package types
import (
"fmt"
"github.com/f1bonacc1/process-compose/src/command"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
@ -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() {
@ -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
}