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
}
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}
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
return value
return value, nil
}
func (cache *TreeCache) buildTree(key TreeCacheKey) *FileTree {
newTree := StackTreeRange(cache.refTrees, key.bottomTreeStart, key.bottomTreeStop)
func (cache *TreeCache) buildTree(key TreeCacheKey) (*FileTree, error) {
newTree, err := StackTreeRange(cache.refTrees, key.bottomTreeStart, key.bottomTreeStop)
if err != nil {
return nil, err
}
for idx := key.topTreeStart; idx <= key.topTreeStop; idx++ {
err := newTree.CompareAndMark(cache.refTrees[idx])
if err != nil {
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
// 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
}
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)
@ -66,8 +76,12 @@ func (cache *TreeCache) Build() {
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 {

View File

@ -1,7 +1,6 @@
package filetree
import (
"fmt"
"sort"
"github.com/sirupsen/logrus"
@ -63,14 +62,22 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) {
sizeBytes += curNode.Data.FileInfo.Size
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())
if err != nil {
logrus.Debug(fmt.Sprintf("CurrentTree: %d : %s", currentTree, err))
} else if previousTreeNode.Data.FileInfo.IsDir {
return err
}
if previousTreeNode.Data.FileInfo.IsDir {
err = previousTreeNode.VisitDepthChildFirst(sizer, nil)
if err != nil {
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
func StackTreeRange(trees []*FileTree, start, stop int) *FileTree {
func StackTreeRange(trees []*FileTree, start, stop int) (*FileTree, error) {
tree := trees[0].Copy()
for idx := start; idx <= stop; idx++ {
err := tree.Stack(trees[idx])
if err != nil {
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}
StackTreeRange(trees, 0, 2)
_, err = StackTreeRange(trees, 0, 2)
if err != nil {
t.Fatal(err)
}
}
func TestRemoveOnIterate(t *testing.T) {

View File

@ -2,6 +2,7 @@ package runtime
import (
"fmt"
"github.com/sirupsen/logrus"
"github.com/wagoodman/dive/dive"
"github.com/wagoodman/dive/runtime/ci"
"github.com/wagoodman/dive/runtime/export"
@ -95,7 +96,11 @@ func Run(options Options) {
fmt.Println(utils.TitleFormat("Building cache..."))
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
// 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)
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 {
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
visitor := func(node *filetree.FileNode) error {
@ -112,7 +116,7 @@ func (vm *FileTreeViewModel) setTreeByLayer(bottomTreeStart, bottomTreeStop, top
}
return nil
}
err := vm.ModelTree.VisitDepthChildFirst(visitor, nil)
err = vm.ModelTree.VisitDepthChildFirst(visitor, nil)
if err != nil {
logrus.Errorf("unable to propagate layer tree: %+v", 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)
}
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()
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) {

View File

@ -251,20 +251,32 @@ func layout(g *gocui.Gui) error {
view, viewErr = g.SetView(Controllers.Layer.Name, -1, -1+headerRows, splitCols, layersHeight)
header, headerErr = g.SetView(Controllers.Layer.Name+"header", -1, -1, splitCols, headerRows)
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 {
logrus.Error("unable to set view to layer", err)
return err
}
// 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
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)
if isNewView(viewErr, headerErr) {
_ = Controllers.Details.Setup(view, header)
err = Controllers.Details.Setup(view, header)
if err != nil {
return err
}
}
// 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)
header, headerErr = g.SetView(Controllers.Tree.Name+"header", splitCols, -1, debugCols, headerRows-offset)
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
view, viewErr = g.SetView(Controllers.Status.Name, -1, maxY-statusBarHeight-statusBarIndex, maxX, maxY-(statusBarIndex-1))
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
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))
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
}
// Update refreshes the state objects for future rendering.
func Update() {
func Update() error {
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.
func Render() {
func Render() error {
for _, view := range Controllers.lookup {
if view.IsVisible() {
_ = view.Render()
err := view.Render()
if err != nil {
return err
}
}
}
return nil
}
// 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.
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.Header = color.New(color.Bold).SprintFunc()
@ -335,20 +371,20 @@ func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) {
var err error
GlobalKeybindings.quit, err = keybinding.ParseAll(viper.GetString("keybinding.quit"))
if err != nil {
logrus.Error(err)
return err
}
GlobalKeybindings.toggleView, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-view"))
if err != nil {
logrus.Error(err)
return err
}
GlobalKeybindings.filterView, err = keybinding.ParseAll(viper.GetString("keybinding.filter-files"))
if err != nil {
logrus.Error(err)
return err
}
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
logrus.Error(err)
return err
}
utils.SetUi(g)
defer g.Close()
@ -358,7 +394,11 @@ func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) {
Controllers.Layer = NewLayerController("side", g, analysis.Layers)
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.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
Update()
Render()
err = Update()
if err != nil {
return err
}
err = Render()
if err != nil {
return err
}
if err := keyBindings(g); err != nil {
logrus.Error("keybinding error: ", err)
return err
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
logrus.Error("main loop error: ", err)
return err
}
utils.Exit(0)
return nil
}