浏览代码

Add feature support

Vadim Markovtsev 7 年之前
父节点
当前提交
1868f15190
共有 4 个文件被更改,包括 104 次插入19 次删除
  1. 5 0
      diff_refiner.go
  2. 51 19
      pipeline.go
  3. 33 0
      pipeline_test.go
  4. 15 0
      uast.go

+ 5 - 0
diff_refiner.go

@@ -21,6 +21,11 @@ func (ref *FileDiffRefiner) Requires() []string {
 	return arr[:]
 }
 
+func (ref *FileDiffRefiner) Features() []string {
+	arr := [...]string{"uast"}
+	return arr[:]
+}
+
 func (ref *FileDiffRefiner) Construct(facts map[string]interface{}) {}
 
 func (ref *FileDiffRefiner) Initialize(repository *git.Repository) {

+ 51 - 19
pipeline.go

@@ -38,6 +38,13 @@ type PipelineItem interface {
 	Finalize() interface{}
 }
 
+type FeaturedPipelineItem interface {
+	PipelineItem
+	// Features returns the list of names which enable this item to be automatically inserted
+	// in Pipeline.DeployItem().
+	Features() []string
+}
+
 type PipelineItemRegistry struct {
 	provided map[string][]reflect.Type
 }
@@ -85,18 +92,24 @@ type Pipeline struct {
 	// repository points to the analysed Git repository struct from go-git.
 	repository *git.Repository
 
-	// items are the registered analysers in the pipeline.
+	// items are the registered building blocks in the pipeline. The order defines the
+	// execution sequence.
 	items []PipelineItem
 
-	// plan is the resolved execution sequence.
-	plan []PipelineItem
-
 	// the collection of parameters to create items.
 	facts map[string]interface{}
+
+	// Feature flags which enable the corresponding items.
+	features map[string]bool
 }
 
 func NewPipeline(repository *git.Repository) *Pipeline {
-	return &Pipeline{repository: repository, items: []PipelineItem{}, plan: []PipelineItem{}}
+	return &Pipeline{
+		repository: repository,
+		items: []PipelineItem{},
+		facts: map[string]interface{}{},
+		features: map[string]bool{},
+	}
 }
 
 func (pipeline *Pipeline) GetFact(name string) interface{} {
@@ -107,6 +120,14 @@ func (pipeline *Pipeline) SetFact(name string, value interface{}) {
 	pipeline.facts[name] = value
 }
 
+func (pipeline *Pipeline) GetFeature(name string) bool {
+	return pipeline.features[name]
+}
+
+func (pipeline *Pipeline) SetFeature(name string) {
+	pipeline.features[name] = true
+}
+
 func (pipeline *Pipeline) DeployItem(item PipelineItem) PipelineItem {
 	queue := []PipelineItem{}
 	queue = append(queue, item)
@@ -119,6 +140,19 @@ func (pipeline *Pipeline) DeployItem(item PipelineItem) PipelineItem {
 		for _, dep := range head.Requires() {
 		  for _, sibling := range Registry.Summon(dep) {
 			  if _, exists := added[sibling.Name()]; !exists {
+				  disabled := false
+				  // If this item supports features, check them against the activated in pipeline.features
+				  if fpi, matches := interface{}(sibling).(FeaturedPipelineItem); matches {
+					  for _, feature := range fpi.Features() {
+						  if !pipeline.features[feature] {
+							  disabled = true
+							  break
+						  }
+					  }
+				  }
+				  if disabled {
+					  continue
+				  }
 				  added[sibling.Name()] = sibling
 				  queue = append(queue, sibling)
 				  pipeline.AddItem(sibling)
@@ -171,7 +205,7 @@ func (pipeline *Pipeline) Commits() []*object.Commit {
 	return result
 }
 
-func (pipeline *Pipeline) Initialize(facts map[string]interface{}) {
+func (pipeline *Pipeline) resolve(dumpPath string) {
 	graph := toposort.NewGraph()
 	name2item := map[string]PipelineItem{}
 	ambiguousMap := map[string][]string{}
@@ -256,30 +290,28 @@ func (pipeline *Pipeline) Initialize(facts map[string]interface{}) {
 			graph.ReindexNode(key)
 		}
 	}
-	if dumpPath, exists := facts["Pipeline.DumpPath"].(string); exists {
-		ioutil.WriteFile(dumpPath, []byte(graph.Serialize([]string{})), 0666)
-	}
 	var graphCopy *toposort.Graph
-	if _, exists := facts["Pipeline.DumpPath"].(string); exists {
+	if dumpPath != "" {
 		graphCopy = graph.Copy()
 	}
 	strplan, ok := graph.Toposort()
 	if !ok {
 		panic("Failed to resolve pipeline dependencies.")
 	}
+	pipeline.items = make([]PipelineItem, 0, len(pipeline.items))
 	for _, key := range strplan {
-		item, ok := name2item[key]
-		if ok {
-			pipeline.plan = append(pipeline.plan, item)
+		if item, ok := name2item[key]; ok {
+			pipeline.items = append(pipeline.items, item)
 		}
 	}
-	if len(pipeline.plan) != len(pipeline.items) {
-		panic("Internal pipeline dependency resolution error.")
-	}
-	if dumpPath, exists := facts["Pipeline.DumpPath"].(string); exists {
+	if dumpPath != "" {
 		ioutil.WriteFile(dumpPath, []byte(graphCopy.Serialize(strplan)), 0666)
 	}
-	if dryRun, exists := facts["Pipeline.DryRun"].(bool); exists && dryRun {
+}
+
+func (pipeline *Pipeline) Initialize(facts map[string]interface{}) {
+	pipeline.resolve(facts["Pipeline.DumpPath"].(string))
+	if facts["Pipeline.DryRun"].(bool) {
 		return
 	}
 	for _, item := range pipeline.items {
@@ -303,7 +335,7 @@ func (pipeline *Pipeline) Run(commits []*object.Commit) (map[PipelineItem]interf
 	for index, commit := range commits {
 		onProgress(index, len(commits))
 		state := map[string]interface{}{"commit": commit, "index": index}
-		for _, item := range pipeline.plan {
+		for _, item := range pipeline.items {
 			update, err := item.Consume(state)
 			if err != nil {
 				fmt.Fprintf(os.Stderr, "%s failed on commit #%d %s\n",

+ 33 - 0
pipeline_test.go

@@ -227,6 +227,7 @@ func TestPipelineError(t *testing.T) {
 
 func TestPipelineSerialize(t *testing.T) {
 	pipeline := NewPipeline(testRepository)
+	pipeline.SetFeature("uast")
 	pipeline.DeployItem(&BurndownAnalysis{})
 	facts := map[string]interface{}{}
 	facts["Pipeline.DryRun"] = true
@@ -264,6 +265,38 @@ func TestPipelineSerialize(t *testing.T) {
 }`, dot)
 }
 
+func TestPipelineSerializeNoUast(t *testing.T) {
+	pipeline := NewPipeline(testRepository)
+	// pipeline.SetFeature("uast")
+	pipeline.DeployItem(&BurndownAnalysis{})
+	facts := map[string]interface{}{}
+	facts["Pipeline.DryRun"] = true
+	tmpdir, _ := ioutil.TempDir("", "hercules-")
+	defer os.RemoveAll(tmpdir)
+	dotpath := path.Join(tmpdir, "graph.dot")
+	facts["Pipeline.DumpPath"] = dotpath
+	pipeline.Initialize(facts)
+	bdot, _ := ioutil.ReadFile(dotpath)
+	dot := string(bdot)
+	assert.Equal(t, `digraph Hercules {
+  "6 BlobCache" -> "7 [blob_cache]"
+  "0 DaysSinceStart" -> "3 [day]"
+  "9 FileDiff" -> "10 [file_diff]"
+  "1 IdentityDetector" -> "4 [author]"
+  "8 RenameAnalysis" -> "11 Burndown"
+  "8 RenameAnalysis" -> "9 FileDiff"
+  "2 TreeDiff" -> "5 [changes]"
+  "4 [author]" -> "11 Burndown"
+  "7 [blob_cache]" -> "11 Burndown"
+  "7 [blob_cache]" -> "9 FileDiff"
+  "7 [blob_cache]" -> "8 RenameAnalysis"
+  "5 [changes]" -> "6 BlobCache"
+  "5 [changes]" -> "8 RenameAnalysis"
+  "3 [day]" -> "11 Burndown"
+  "10 [file_diff]" -> "11 Burndown"
+}`, dot)
+}
+
 func init() {
 	cwd, err := os.Getwd()
 	if err == nil {

+ 15 - 0
uast.go

@@ -68,6 +68,11 @@ func (exr *UASTExtractor) Requires() []string {
 	return arr[:]
 }
 
+func (exr *UASTExtractor) Features() []string {
+	arr := [...]string{"uast"}
+	return arr[:]
+}
+
 func (exr *UASTExtractor) Construct(facts map[string]interface{}) {
 	if val, exists := facts["UAST.Endpoint"].(string); exists {
 		exr.Endpoint = val
@@ -242,6 +247,11 @@ func (uc *UASTChanges) Requires() []string {
 	return arr[:]
 }
 
+func (uc *UASTChanges) Features() []string {
+	arr := [...]string{"uast"}
+	return arr[:]
+}
+
 func (uc *UASTChanges) Construct(facts map[string]interface{}) {}
 
 func (uc *UASTChanges) Initialize(repository *git.Repository) {
@@ -300,6 +310,11 @@ func (saver *UASTChangesSaver) Requires() []string {
 	return arr[:]
 }
 
+func (saver *UASTChangesSaver) Features() []string {
+	arr := [...]string{"uast"}
+	return arr[:]
+}
+
 func (saver *UASTChangesSaver) Construct(facts map[string]interface{}) {}
 
 func (saver *UASTChangesSaver) Initialize(repository *git.Repository) {