Debugging

Whether developing a new feature or fixing a bug, using the automated test suite together with a debugger is a potent combination. This guide here can’t substitute for full documentation on a given debugger; but it might help you get started debugging Warewulf.

Validating the code with vet

The Warewulf Makefile includes a vet target which runs go vet on the full codebase.

$ make vet

Running the full test suite

The Warewulf Makefile includes a test target which runs the full test suite.

$ make test

Using delve

If you have a failing test but you’re having trouble tracking down why, try using a debugger to step through the test. These instructions use delve.

Installing delve

You can install delve as a regular user directly with Go.

$ go install github.com/go-delve/delve/cmd/dlv@latest

The dlv binary will be installed by default at $HOME/go/bin/dlv. You can, of course, add $HOME/go/bin to your path if you prefer.

$ PATH=$HOME/go/bin:$PATH

Running delve against a specific test

You can use delve to specifically run the test suite and, even more specifically, a single failing test. In this example delve is instructed to run the tests for Warewulf’s node package, and specifically the Test_GetAllNodeInfoDefaults test.

$ dlv test github.com/warewulf/warewulf/internal/pkg/node -- -test.v -test.run Test_GetAllNodeInfoDefaults
Type 'help' for list of commands.
(dlv) break node.Test_GetAllNodeInfoDefaults
Breakpoint 1 set at 0x26c0d0 for github.com/warewulf/warewulf/internal/pkg/node.Test_GetAllNodeInfoDefaults() ./internal/pkg/node/nodeyaml_test.go:51

Setting a breakpoint at node.Test_GetAllNodeInfoDefaults pauses execution once the test starts, and allows us to continue through all the setup prior to that point.

(dlv) continue
=== RUN   Test_GetAllNodeInfoDefaults
> github.com/warewulf/warewulf/internal/pkg/node.Test_GetAllNodeInfoDefaults() ./internal/pkg/node/nodeyaml_test.go:51 (hits goroutine(35):1 total:1) (PC: 0x26c0d0)
    46:              assert.Contains(t, nodeYaml.Nodes, "test_node")
    47:              assert.Equal(t, "A single node", nodeYaml.Nodes["test_node"].Comment)
    48:      }
    49:
    50:
=>  51:      func Test_GetAllNodeInfoDefaults(t *testing.T) {
    52:              file, writeErr := writeTestConfigFile(`
    53:      nodes:
    54:        test_node: {}`)
    55:              if file != nil {
    56:                      defer os.Remove(file.Name())

Helpful commands from here include

next

Execute the current line (marked by =>) and proceed to the next line.

step

Execute the current line (marked by =>) and proceed to the next line, potentially moving into a function call.

list

Display a contextual Go code listing, marking the next instruction.

locals

Display all local variables in the current scope.

print

Display (in detail) the value of a single variable from the current scope.

Read about other commands available within delve using the help command.

Example debugging session

$ ~/go/bin/dlv test github.com/warewulf/warewulf/internal/pkg/node -- -test.v -test.run Test_GetAllNodeInfoDefaults
Type 'help' for list of commands.

(dlv) break node.Test_GetAllNodeInfoDefaults
Breakpoint 1 set at 0x26c0d0 for github.com/warewulf/warewulf/internal/pkg/node.Test_GetAllNodeInfoDefaults() ./internal/pkg/node/nodeyaml_test.go:51

(dlv) break nodeinfo.go:417
Breakpoint 2 set at 0x267f18 for github.com/warewulf/warewulf/internal/pkg/node.NewNodeInfo() ./internal/pkg/node/nodeinfo.go:417

(dlv) continue
=== RUN   Test_GetAllNodeInfoDefaults
> github.com/warewulf/warewulf/internal/pkg/node.Test_GetAllNodeInfoDefaults() ./internal/pkg/node/nodeyaml_test.go:51 (hits goroutine(19):1 total:1) (PC: 0x26c0d0)
46:          assert.Contains(t, nodeYaml.Nodes, "test_node")
47:          assert.Equal(t, "A single node", nodeYaml.Nodes["test_node"].Comment)
48:  }
49:
50:
=>  51:      func Test_GetAllNodeInfoDefaults(t *testing.T) {
52:          file, writeErr := writeTestConfigFile(`
53:  nodes:
54:    test_node: {}`)
55:          if file != nil {
56:                  defer os.Remove(file.Name())

(dlv) continue
WARN   : Error reading UNDEF/warewulf/defaults.conf: open UNDEF/warewulf/defaults.conf: no such file or directory
> github.com/warewulf/warewulf/internal/pkg/node.NewNodeInfo() ./internal/pkg/node/nodeinfo.go:417 (hits goroutine(19):1 total:1) (PC: 0x267f18)
412:                 defaultNodeConf.NetDevs = nil
413:                 nodeInfo.SetDefFrom(defaultNodeConf)
414:         }
415:
416:         // Load normal attributes
=> 417:              if nodeConf != nil {
418:                 // If no profiles are included, automatically include the
419:                 // default profile.
420:                 if len(nodeConf.Profiles) == 0 {
421:                         nodeInfo.Profiles.SetSlice([]string{"default"})
422:                 } else {

(dlv) next
> github.com/warewulf/warewulf/internal/pkg/node.NewNodeInfo() ./internal/pkg/node/nodeinfo.go:420 (PC: 0x267f24)
415:
416:         // Load normal attributes
417:         if nodeConf != nil {
418:                 // If no profiles are included, automatically include the
419:                 // default profile.
=> 420:                      if len(nodeConf.Profiles) == 0 {
421:                         nodeInfo.Profiles.SetSlice([]string{"default"})
422:                 } else {
423:                         nodeInfo.Profiles.SetSlice(nodeConf.Profiles)
424:                 }
425:

(dlv) next
> github.com/warewulf/warewulf/internal/pkg/node.NewNodeInfo() ./internal/pkg/node/nodeinfo.go:421 (PC: 0x267f3c)
416:         // Load normal attributes
417:         if nodeConf != nil {
418:                 // If no profiles are included, automatically include the
419:                 // default profile.
420:                 if len(nodeConf.Profiles) == 0 {
=> 421:                              nodeInfo.Profiles.SetSlice([]string{"default"})
422:                 } else {
423:                         nodeInfo.Profiles.SetSlice(nodeConf.Profiles)
424:                 }
425:
426:                 nodeInfo.SetFrom(nodeConf)

(dlv) next
> github.com/warewulf/warewulf/internal/pkg/node.NewNodeInfo() ./internal/pkg/node/nodeinfo.go:426 (PC: 0x267fec)
421:                         nodeInfo.Profiles.SetSlice([]string{"default"})
422:                 } else {
423:                         nodeInfo.Profiles.SetSlice(nodeConf.Profiles)
424:                 }
425:
=> 426:                      nodeInfo.SetFrom(nodeConf)
427:         }
428:
429:         // Load default attributes for each NetDev
430:         if defaultNetDevConf != nil {
431:                 for _, netdev := range nodeInfo.NetDevs {

(dlv) next
> github.com/warewulf/warewulf/internal/pkg/node.NewNodeInfo() ./internal/pkg/node/nodeinfo.go:430 (PC: 0x268000)
425:
426:                 nodeInfo.SetFrom(nodeConf)
427:         }
428:
429:         // Load default attributes for each NetDev
=> 430:              if defaultNetDevConf != nil {
431:                 for _, netdev := range nodeInfo.NetDevs {
432:                         netdev.SetDefFrom(defaultNetDevConf)
433:                 }
434:         }
435:

(dlv) print nodeInfo
github.com/warewulf/warewulf/internal/pkg/node.NodeInfo {
Id: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 1, cap: 1, [
"test_node",
],
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 0, cap: 0, nil,},
Comment: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 0, cap: 0, nil,},
ClusterName: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 0, cap: 0, nil,},
ContainerName: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 0, cap: 0, nil,},
Ipxe: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 1, cap: 1, ["default"],},
RuntimeOverlay: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 1, cap: 1, ["generic"],},
SystemOverlay: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 1, cap: 1, ["wwinit"],},
Root: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 1, cap: 1, [
"initramfs",
],},
Discoverable: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 0, cap: 0, nil,},
Init: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 1, cap: 1, [
"/sbin/init",
],},
AssetKey: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 0, cap: 0, nil,},
Kernel: *github.com/warewulf/warewulf/internal/pkg/node.KernelEntry {
Override: (*"github.com/warewulf/warewulf/internal/pkg/node.Entry")(0x4000158370),
Args: (*"github.com/warewulf/warewulf/internal/pkg/node.Entry")(0x40001583c8),},
Ipmi: *github.com/warewulf/warewulf/internal/pkg/node.IpmiEntry {
Ipaddr: (*"github.com/warewulf/warewulf/internal/pkg/node.Entry")(0x40001b6600),
Netmask: (*"github.com/warewulf/warewulf/internal/pkg/node.Entry")(0x40001b6658),
Port: (*"github.com/warewulf/warewulf/internal/pkg/node.Entry")(0x40001b66b0),
Gateway: (*"github.com/warewulf/warewulf/internal/pkg/node.Entry")(0x40001b6708),
UserName: (*"github.com/warewulf/warewulf/internal/pkg/node.Entry")(0x40001b6760),
Password: (*"github.com/warewulf/warewulf/internal/pkg/node.Entry")(0x40001b67b8),
Interface: (*"github.com/warewulf/warewulf/internal/pkg/node.Entry")(0x40001b6810),
Write: (*"github.com/warewulf/warewulf/internal/pkg/node.Entry")(0x40001b6868),
Tags: map[string]*github.com/warewulf/warewulf/internal/pkg/node.Entry [],},
Profiles: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 1, cap: 1, ["default"],
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 1, cap: 1, ["default"],},
PrimaryNetDev: github.com/warewulf/warewulf/internal/pkg/node.Entry {
value: []string len: 0, cap: 0, nil,
altvalue: []string len: 0, cap: 0, nil,
from: "",
def: []string len: 0, cap: 0, nil,},
NetDevs: map[string]*github.com/warewulf/warewulf/internal/pkg/node.NetDevEntry [],
Tags: map[string]*github.com/warewulf/warewulf/internal/pkg/node.Entry [],}