better error propagation on startup

This commit is contained in:
Alex Goodman 2019-10-06 12:15:58 -04:00
parent 56e8530dea
commit 26d37baabd
No known key found for this signature in database
GPG Key ID: 98AF011C5C78EB7E
8 changed files with 139 additions and 44 deletions

View File

@ -13,29 +13,36 @@ type TreeCache struct {
cache map[TreeCacheKey]*FileTree cache map[TreeCacheKey]*FileTree
} }
func (cache *TreeCache) Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) *FileTree { func (cache *TreeCache) Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) (*FileTree, error) {
key := TreeCacheKey{bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop} key := TreeCacheKey{bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop}
if value, exists := cache.cache[key]; exists { if value, exists := cache.cache[key]; exists {
return value return value, nil
} }
value := cache.buildTree(key) value, err := cache.buildTree(key)
if err != nil {
return nil, err
}
cache.cache[key] = value cache.cache[key] = value
return value return value, nil
} }
func (cache *TreeCache) buildTree(key TreeCacheKey) *FileTree { func (cache *TreeCache) buildTree(key TreeCacheKey) (*FileTree, error) {
newTree := StackTreeRange(cache.refTrees, key.bottomTreeStart, key.bottomTreeStop) newTree, err := StackTreeRange(cache.refTrees, key.bottomTreeStart, key.bottomTreeStop)
if err != nil {
return nil, err
}
for idx := key.topTreeStart; idx <= key.topTreeStop; idx++ { for idx := key.topTreeStart; idx <= key.topTreeStop; idx++ {
err := newTree.CompareAndMark(cache.refTrees[idx]) err := newTree.CompareAndMark(cache.refTrees[idx])
if err != nil { if err != nil {
logrus.Errorf("unable to build tree: %+v", err) logrus.Errorf("unable to build tree: %+v", err)
return nil, err
} }
} }
return newTree return newTree, nil
} }
func (cache *TreeCache) Build() { func (cache *TreeCache) Build() error {
var bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int var bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int
// case 1: layer compare (top tree SIZE is fixed (BUT floats forward), Bottom tree SIZE changes) // case 1: layer compare (top tree SIZE is fixed (BUT floats forward), Bottom tree SIZE changes)
@ -51,7 +58,10 @@ func (cache *TreeCache) Build() {
topTreeStart = selectIdx topTreeStart = selectIdx
} }
cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop) _, err := cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop)
if err != nil {
return err
}
} }
// case 2: aggregated compare (bottom tree is ENTIRELY fixed, top tree SIZE changes) // case 2: aggregated compare (bottom tree is ENTIRELY fixed, top tree SIZE changes)
@ -66,8 +76,12 @@ func (cache *TreeCache) Build() {
topTreeStart = 1 topTreeStart = 1
} }
cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop) _, err := cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop)
if err != nil {
return err
}
} }
return nil
} }
func NewFileTreeCache(refTrees []*FileTree) TreeCache { func NewFileTreeCache(refTrees []*FileTree) TreeCache {

View File

@ -1,7 +1,6 @@
package filetree package filetree
import ( import (
"fmt"
"sort" "sort"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -63,14 +62,22 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) {
sizeBytes += curNode.Data.FileInfo.Size sizeBytes += curNode.Data.FileInfo.Size
return nil return nil
} }
stackedTree := StackTreeRange(trees, 0, currentTree-1) stackedTree, err := StackTreeRange(trees, 0, currentTree-1)
if err != nil {
logrus.Errorf("unable to stack tree range: %+v", err)
return err
}
previousTreeNode, err := stackedTree.GetNode(node.Path()) previousTreeNode, err := stackedTree.GetNode(node.Path())
if err != nil { if err != nil {
logrus.Debug(fmt.Sprintf("CurrentTree: %d : %s", currentTree, err)) return err
} else if previousTreeNode.Data.FileInfo.IsDir { }
if previousTreeNode.Data.FileInfo.IsDir {
err = previousTreeNode.VisitDepthChildFirst(sizer, nil) err = previousTreeNode.VisitDepthChildFirst(sizer, nil)
if err != nil { if err != nil {
logrus.Errorf("unable to propagate whiteout dir: %+v", err) logrus.Errorf("unable to propagate whiteout dir: %+v", err)
return err
} }
} }

View File

@ -367,14 +367,15 @@ func (tree *FileTree) markRemoved(path string) error {
} }
// StackTreeRange combines an array of trees into a single tree // StackTreeRange combines an array of trees into a single tree
func StackTreeRange(trees []*FileTree, start, stop int) *FileTree { func StackTreeRange(trees []*FileTree, start, stop int) (*FileTree, error) {
tree := trees[0].Copy() tree := trees[0].Copy()
for idx := start; idx <= stop; idx++ { for idx := start; idx <= stop; idx++ {
err := tree.Stack(trees[idx]) err := tree.Stack(trees[idx])
if err != nil { if err != nil {
logrus.Errorf("could not stack tree range: %v", err) logrus.Errorf("could not stack tree range: %v", err)
return nil, err
} }
} }
return tree return tree, nil
} }

View File

@ -745,7 +745,10 @@ func TestStackRange(t *testing.T) {
} }
} }
trees := []*FileTree{lowerTree, upperTree, tree} trees := []*FileTree{lowerTree, upperTree, tree}
StackTreeRange(trees, 0, 2) _, err = StackTreeRange(trees, 0, 2)
if err != nil {
t.Fatal(err)
}
} }
func TestRemoveOnIterate(t *testing.T) { func TestRemoveOnIterate(t *testing.T) {

View File

@ -2,6 +2,7 @@ package runtime
import ( import (
"fmt" "fmt"
"github.com/sirupsen/logrus"
"github.com/wagoodman/dive/dive" "github.com/wagoodman/dive/dive"
"github.com/wagoodman/dive/runtime/ci" "github.com/wagoodman/dive/runtime/ci"
"github.com/wagoodman/dive/runtime/export" "github.com/wagoodman/dive/runtime/export"
@ -95,7 +96,11 @@ func Run(options Options) {
fmt.Println(utils.TitleFormat("Building cache...")) fmt.Println(utils.TitleFormat("Building cache..."))
cache := filetree.NewFileTreeCache(result.RefTrees) cache := filetree.NewFileTreeCache(result.RefTrees)
cache.Build() err := cache.Build()
if err != nil {
logrus.Error(err)
utils.Exit(1)
}
// it appears there is a race condition where termbox.Init() will // it appears there is a race condition where termbox.Init() will
// block nearly indefinitely when running as the first process in // block nearly indefinitely when running as the first process in
@ -104,6 +109,12 @@ func Run(options Options) {
// enough sleep will prevent this behavior (todo: remove this hack) // enough sleep will prevent this behavior (todo: remove this hack)
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
ui.Run(result, cache) err = ui.Run(result, cache)
if err != nil {
logrus.Error(err)
utils.Exit(1)
}
utils.Exit(0)
} }
} }

View File

@ -102,7 +102,11 @@ func (vm *FileTreeViewModel) setTreeByLayer(bottomTreeStart, bottomTreeStop, top
if topTreeStop > len(vm.RefTrees)-1 { if topTreeStop > len(vm.RefTrees)-1 {
return fmt.Errorf("invalid layer index given: %d of %d", topTreeStop, len(vm.RefTrees)-1) return fmt.Errorf("invalid layer index given: %d of %d", topTreeStop, len(vm.RefTrees)-1)
} }
newTree := vm.cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop) newTree, err := vm.cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop)
if err != nil {
logrus.Errorf("unable to fetch layer tree from cache: %+v", err)
return err
}
// preserve vm state on copy // preserve vm state on copy
visitor := func(node *filetree.FileNode) error { visitor := func(node *filetree.FileNode) error {
@ -112,7 +116,7 @@ func (vm *FileTreeViewModel) setTreeByLayer(bottomTreeStart, bottomTreeStop, top
} }
return nil return nil
} }
err := vm.ModelTree.VisitDepthChildFirst(visitor, nil) err = vm.ModelTree.VisitDepthChildFirst(visitor, nil)
if err != nil { if err != nil {
logrus.Errorf("unable to propagate layer tree: %+v", err) logrus.Errorf("unable to propagate layer tree: %+v", err)
return err return err

View File

@ -78,11 +78,18 @@ func initializeTestViewModel(t *testing.T) *FileTreeViewModel {
t.Fatalf("%s: unable to fetch analysis: %v", t.Name(), err) t.Fatalf("%s: unable to fetch analysis: %v", t.Name(), err)
} }
cache := filetree.NewFileTreeCache(result.RefTrees) cache := filetree.NewFileTreeCache(result.RefTrees)
cache.Build() err = cache.Build()
if err != nil {
t.Fatalf("%s: unable to build cache: %+v", t.Name(), err)
}
Formatting.Selected = color.New(color.ReverseVideo, color.Bold).SprintFunc() Formatting.Selected = color.New(color.ReverseVideo, color.Bold).SprintFunc()
return NewFileTreeViewModel(filetree.StackTreeRange(result.RefTrees, 0, 0), result.RefTrees, cache) treeStack, err := filetree.StackTreeRange(result.RefTrees, 0, 0)
if err != nil {
t.Fatalf("%s: unable to create tree ViewModel: %v", t.Name(), err)
}
return NewFileTreeViewModel(treeStack, result.RefTrees, cache)
} }
func runTestCase(t *testing.T, vm *FileTreeViewModel, width, height int, filterRegex *regexp.Regexp) { func runTestCase(t *testing.T, vm *FileTreeViewModel, width, height int, filterRegex *regexp.Regexp) {

View File

@ -251,20 +251,32 @@ func layout(g *gocui.Gui) error {
view, viewErr = g.SetView(Controllers.Layer.Name, -1, -1+headerRows, splitCols, layersHeight) view, viewErr = g.SetView(Controllers.Layer.Name, -1, -1+headerRows, splitCols, layersHeight)
header, headerErr = g.SetView(Controllers.Layer.Name+"header", -1, -1, splitCols, headerRows) header, headerErr = g.SetView(Controllers.Layer.Name+"header", -1, -1, splitCols, headerRows)
if isNewView(viewErr, headerErr) { if isNewView(viewErr, headerErr) {
_ = Controllers.Layer.Setup(view, header) err = Controllers.Layer.Setup(view, header)
if err != nil {
logrus.Error("unable to setup layer controller", err)
return err
}
if _, err = g.SetCurrentView(Controllers.Layer.Name); err != nil { if _, err = g.SetCurrentView(Controllers.Layer.Name); err != nil {
logrus.Error("unable to set view to layer", err)
return err return err
} }
// since we are selecting the view, we should rerender to indicate it is selected // since we are selecting the view, we should rerender to indicate it is selected
_ = Controllers.Layer.Render() err = Controllers.Layer.Render()
if err != nil {
logrus.Error("unable to render layer view", err)
return err
}
} }
// Details // Details
view, viewErr = g.SetView(Controllers.Details.Name, -1, -1+layersHeight+headerRows, splitCols, maxY-bottomRows) view, viewErr = g.SetView(Controllers.Details.Name, -1, -1+layersHeight+headerRows, splitCols, maxY-bottomRows)
header, headerErr = g.SetView(Controllers.Details.Name+"header", -1, -1+layersHeight, splitCols, layersHeight+headerRows) header, headerErr = g.SetView(Controllers.Details.Name+"header", -1, -1+layersHeight, splitCols, layersHeight+headerRows)
if isNewView(viewErr, headerErr) { if isNewView(viewErr, headerErr) {
_ = Controllers.Details.Setup(view, header) err = Controllers.Details.Setup(view, header)
if err != nil {
return err
}
} }
// Filetree // Filetree
@ -275,40 +287,64 @@ func layout(g *gocui.Gui) error {
view, viewErr = g.SetView(Controllers.Tree.Name, splitCols, -1+headerRows-offset, debugCols, maxY-bottomRows) view, viewErr = g.SetView(Controllers.Tree.Name, splitCols, -1+headerRows-offset, debugCols, maxY-bottomRows)
header, headerErr = g.SetView(Controllers.Tree.Name+"header", splitCols, -1, debugCols, headerRows-offset) header, headerErr = g.SetView(Controllers.Tree.Name+"header", splitCols, -1, debugCols, headerRows-offset)
if isNewView(viewErr, headerErr) { if isNewView(viewErr, headerErr) {
_ = Controllers.Tree.Setup(view, header) err = Controllers.Tree.Setup(view, header)
if err != nil {
logrus.Error("unable to setup tree controller", err)
return err
}
}
err = Controllers.Tree.onLayoutChange(resized)
if err != nil {
logrus.Error("unable to setup layer controller onLayoutChange", err)
return err
} }
_ = Controllers.Tree.onLayoutChange(resized)
// Status Bar // Status Bar
view, viewErr = g.SetView(Controllers.Status.Name, -1, maxY-statusBarHeight-statusBarIndex, maxX, maxY-(statusBarIndex-1)) view, viewErr = g.SetView(Controllers.Status.Name, -1, maxY-statusBarHeight-statusBarIndex, maxX, maxY-(statusBarIndex-1))
if isNewView(viewErr, headerErr) { if isNewView(viewErr, headerErr) {
_ = Controllers.Status.Setup(view, nil) err = Controllers.Status.Setup(view, nil)
if err != nil {
logrus.Error("unable to setup status controller", err)
return err
}
} }
// Filter Bar // Filter Bar
view, viewErr = g.SetView(Controllers.Filter.Name, len(Controllers.Filter.headerStr)-1, maxY-filterBarHeight-filterBarIndex, maxX, maxY-(filterBarIndex-1)) view, viewErr = g.SetView(Controllers.Filter.Name, len(Controllers.Filter.headerStr)-1, maxY-filterBarHeight-filterBarIndex, maxX, maxY-(filterBarIndex-1))
header, headerErr = g.SetView(Controllers.Filter.Name+"header", -1, maxY-filterBarHeight-filterBarIndex, len(Controllers.Filter.headerStr), maxY-(filterBarIndex-1)) header, headerErr = g.SetView(Controllers.Filter.Name+"header", -1, maxY-filterBarHeight-filterBarIndex, len(Controllers.Filter.headerStr), maxY-(filterBarIndex-1))
if isNewView(viewErr, headerErr) { if isNewView(viewErr, headerErr) {
_ = Controllers.Filter.Setup(view, header) err = Controllers.Filter.Setup(view, header)
if err != nil {
logrus.Error("unable to setup filter controller", err)
return err
}
} }
return nil return nil
} }
// Update refreshes the state objects for future rendering. // Update refreshes the state objects for future rendering.
func Update() { func Update() error {
for _, view := range Controllers.lookup { for _, view := range Controllers.lookup {
_ = view.Update() err := view.Update()
if err != nil {
return err
}
} }
return nil
} }
// Render flushes the state objects to the screen. // Render flushes the state objects to the screen.
func Render() { func Render() error {
for _, view := range Controllers.lookup { for _, view := range Controllers.lookup {
if view.IsVisible() { if view.IsVisible() {
_ = view.Render() err := view.Render()
if err != nil {
return err
}
} }
} }
return nil
} }
// renderStatusOption formats key help bindings-to-title pairs. // renderStatusOption formats key help bindings-to-title pairs.
@ -321,7 +357,7 @@ func renderStatusOption(control, title string, selected bool) string {
} }
// Run is the UI entrypoint. // Run is the UI entrypoint.
func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) { func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) error {
Formatting.Selected = color.New(color.ReverseVideo, color.Bold).SprintFunc() Formatting.Selected = color.New(color.ReverseVideo, color.Bold).SprintFunc()
Formatting.Header = color.New(color.Bold).SprintFunc() Formatting.Header = color.New(color.Bold).SprintFunc()
@ -335,20 +371,20 @@ func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) {
var err error var err error
GlobalKeybindings.quit, err = keybinding.ParseAll(viper.GetString("keybinding.quit")) GlobalKeybindings.quit, err = keybinding.ParseAll(viper.GetString("keybinding.quit"))
if err != nil { if err != nil {
logrus.Error(err) return err
} }
GlobalKeybindings.toggleView, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-view")) GlobalKeybindings.toggleView, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-view"))
if err != nil { if err != nil {
logrus.Error(err) return err
} }
GlobalKeybindings.filterView, err = keybinding.ParseAll(viper.GetString("keybinding.filter-files")) GlobalKeybindings.filterView, err = keybinding.ParseAll(viper.GetString("keybinding.filter-files"))
if err != nil { if err != nil {
logrus.Error(err) return err
} }
g, err := gocui.NewGui(gocui.OutputNormal) g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil { if err != nil {
logrus.Error(err) return err
} }
utils.SetUi(g) utils.SetUi(g)
defer g.Close() defer g.Close()
@ -358,7 +394,11 @@ func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) {
Controllers.Layer = NewLayerController("side", g, analysis.Layers) Controllers.Layer = NewLayerController("side", g, analysis.Layers)
Controllers.lookup[Controllers.Layer.Name] = Controllers.Layer Controllers.lookup[Controllers.Layer.Name] = Controllers.Layer
Controllers.Tree = NewFileTreeController("main", g, filetree.StackTreeRange(analysis.RefTrees, 0, 0), analysis.RefTrees, cache) treeStack, err := filetree.StackTreeRange(analysis.RefTrees, 0, 0)
if err != nil {
return err
}
Controllers.Tree = NewFileTreeController("main", g, treeStack, analysis.RefTrees, cache)
Controllers.lookup[Controllers.Tree.Name] = Controllers.Tree Controllers.lookup[Controllers.Tree.Name] = Controllers.Tree
Controllers.Status = NewStatusController("status", g) Controllers.Status = NewStatusController("status", g)
@ -381,15 +421,23 @@ func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) {
// } // }
// perform the first update and render now that all resources have been loaded // perform the first update and render now that all resources have been loaded
Update() err = Update()
Render() if err != nil {
return err
}
err = Render()
if err != nil {
return err
}
if err := keyBindings(g); err != nil { if err := keyBindings(g); err != nil {
logrus.Error("keybinding error: ", err) return err
} }
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
logrus.Error("main loop error: ", err) logrus.Error("main loop error: ", err)
return err
} }
utils.Exit(0) return nil
} }