factor dnsmasq code into testing helper, verify dhcp6 client id
This commit is contained in:
parent
7d2205c295
commit
1e62de50bd
@ -1,25 +1,18 @@
|
|||||||
package integration_test
|
package integration_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"router7/internal/dhcp4"
|
"router7/internal/dhcp4"
|
||||||
|
"router7/internal/testing/dnsmasq"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/gopacket"
|
|
||||||
"github.com/google/gopacket/layers"
|
|
||||||
"github.com/google/gopacket/pcap"
|
|
||||||
"github.com/google/gopacket/pcapgo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDHCPv4(t *testing.T) {
|
func TestDHCPv4(t *testing.T) {
|
||||||
@ -51,77 +44,41 @@ func TestDHCPv4(t *testing.T) {
|
|||||||
ready.Close() // dnsmasq will re-create the file anyway
|
ready.Close() // dnsmasq will re-create the file anyway
|
||||||
defer os.Remove(ready.Name()) // dnsmasq does not clean up its pid file
|
defer os.Remove(ready.Name()) // dnsmasq does not clean up its pid file
|
||||||
|
|
||||||
var stderr bytes.Buffer
|
dnsmasq := dnsmasq.Run(t)
|
||||||
dnsmasq := exec.Command("ip", "netns", "exec", ns, "dnsmasq",
|
defer dnsmasq.Kill()
|
||||||
"--keep-in-foreground", // cannot use --no-daemon because we need --pid-file
|
|
||||||
"--log-facility=-", // log to stderr
|
|
||||||
"--pid-file="+ready.Name(),
|
|
||||||
"--bind-interfaces",
|
|
||||||
"--interface=veth0b",
|
|
||||||
"--dhcp-range=192.168.23.2,192.168.23.10",
|
|
||||||
"--dhcp-range=::1,::10,constructor:veth0b",
|
|
||||||
"--dhcp-authoritative", // eliminate timeouts
|
|
||||||
"--no-ping", // disable ICMP confirmation of unused addresses to eliminate tedious timeout
|
|
||||||
"--leasefile-ro", // do not create a lease database
|
|
||||||
)
|
|
||||||
dnsmasq.Stdout = os.Stdout
|
|
||||||
dnsmasq.Stderr = &stderr
|
|
||||||
if err := dnsmasq.Start(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
done := false // TODO: fix data race
|
|
||||||
go func() {
|
|
||||||
err := dnsmasq.Wait()
|
|
||||||
if !done {
|
|
||||||
t.Fatalf("dnsmasq exited prematurely: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
defer func() {
|
|
||||||
done = true
|
|
||||||
dnsmasq.Process.Kill()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// TODO(later): use inotify instead of polling
|
// f, err := os.Create("/tmp/pcap")
|
||||||
// Wait for dnsmasq to write its process id, at which point it is already
|
// if err != nil {
|
||||||
// listening for requests.
|
// t.Fatal(err)
|
||||||
for {
|
// }
|
||||||
b, err := ioutil.ReadFile(ready.Name())
|
// defer f.Close()
|
||||||
if err != nil {
|
// pcapw := pcapgo.NewWriter(f)
|
||||||
t.Fatal(err)
|
// if err := pcapw.WriteFileHeader(1600, layers.LinkTypeEthernet); err != nil {
|
||||||
}
|
// t.Fatal(err)
|
||||||
if strings.TrimSpace(string(b)) == strconv.Itoa(dnsmasq.Process.Pid) {
|
// }
|
||||||
break
|
// handle, err := pcap.OpenLive("veth0a", 1600, true, pcap.BlockForever)
|
||||||
}
|
// if err != nil {
|
||||||
time.Sleep(10 * time.Millisecond)
|
// t.Fatal(err)
|
||||||
}
|
// }
|
||||||
|
// pkgsrc := gopacket.NewPacketSource(handle, handle.LinkType())
|
||||||
f, err := os.Create("/tmp/pcap")
|
// closed := make(chan struct{})
|
||||||
if err != nil {
|
// go func() {
|
||||||
t.Fatal(err)
|
// for packet := range pkgsrc.Packets() {
|
||||||
}
|
// if packet.Layer(layers.LayerTypeDHCPv4) != nil {
|
||||||
defer f.Close()
|
// log.Printf("packet: %+v", packet)
|
||||||
pcapw := pcapgo.NewWriter(f)
|
// if err := pcapw.WritePacket(packet.Metadata().CaptureInfo, packet.Data()); err != nil {
|
||||||
if err := pcapw.WriteFileHeader(1600, layers.LinkTypeEthernet); err != nil {
|
// t.Fatalf("pcap.WritePacket(): %v", err)
|
||||||
t.Fatal(err)
|
// }
|
||||||
}
|
// }
|
||||||
handle, err := pcap.OpenLive("veth0a", 1600, true, pcap.BlockForever)
|
// }
|
||||||
if err != nil {
|
// close(closed)
|
||||||
t.Fatal(err)
|
// }()
|
||||||
}
|
// // TODO: test the capture daemon
|
||||||
pkgsrc := gopacket.NewPacketSource(handle, handle.LinkType())
|
// defer func() {
|
||||||
closed := make(chan struct{})
|
// time.Sleep(1 * time.Second)
|
||||||
go func() {
|
// handle.Close()
|
||||||
for packet := range pkgsrc.Packets() {
|
// <-closed
|
||||||
if packet.Layer(layers.LayerTypeDHCPv4) != nil {
|
// }()
|
||||||
log.Printf("packet: %+v", packet)
|
|
||||||
if err := pcapw.WritePacket(packet.Metadata().CaptureInfo, packet.Data()); err != nil {
|
|
||||||
t.Fatalf("pcap.WritePacket(): %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(closed)
|
|
||||||
}()
|
|
||||||
// TODO: test the capture daemon
|
|
||||||
|
|
||||||
iface, err := net.InterfaceByName("veth0a")
|
iface, err := net.InterfaceByName("veth0a")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -147,27 +104,8 @@ func TestDHCPv4(t *testing.T) {
|
|||||||
// TODO: alternatively, replace bytes.Buffer with a pipe and read from that
|
// TODO: alternatively, replace bytes.Buffer with a pipe and read from that
|
||||||
time.Sleep(100 * time.Millisecond) // give dnsmasq some time to process the DHCPRELEASE
|
time.Sleep(100 * time.Millisecond) // give dnsmasq some time to process the DHCPRELEASE
|
||||||
|
|
||||||
// Kill dnsmasq to flush its log
|
dnsmasq.Kill() // to flush logs
|
||||||
done = true
|
got := dnsmasq.Actions()
|
||||||
dnsmasq.Process.Kill()
|
|
||||||
|
|
||||||
mac := iface.HardwareAddr.String()
|
|
||||||
lines := strings.Split(strings.TrimSpace(stderr.String()), "\n")
|
|
||||||
var dhcpActionRe = regexp.MustCompile(` (DHCP[^ ]*)`)
|
|
||||||
var actions []string
|
|
||||||
for _, line := range lines {
|
|
||||||
if !strings.HasPrefix(line, "dnsmasq-dhcp") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !strings.Contains(line, mac) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
matches := dhcpActionRe.FindStringSubmatch(line)
|
|
||||||
if matches == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
actions = append(actions, matches[1])
|
|
||||||
}
|
|
||||||
want := []string{
|
want := []string{
|
||||||
"DHCPDISCOVER(veth0b)",
|
"DHCPDISCOVER(veth0b)",
|
||||||
"DHCPOFFER(veth0b)",
|
"DHCPOFFER(veth0b)",
|
||||||
@ -175,11 +113,14 @@ func TestDHCPv4(t *testing.T) {
|
|||||||
"DHCPACK(veth0b)",
|
"DHCPACK(veth0b)",
|
||||||
"DHCPRELEASE(veth0b)",
|
"DHCPRELEASE(veth0b)",
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(actions, want); diff != "" {
|
actionOnly := func(line string) string {
|
||||||
|
result := line
|
||||||
|
if idx := strings.Index(result, " "); idx > -1 {
|
||||||
|
return result[:idx]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(got, want, cmp.Transformer("ActionOnly", actionOnly)); diff != "" {
|
||||||
t.Errorf("dnsmasq log does not contain expected DHCP sequence: diff (-got +want):\n%s", diff)
|
t.Errorf("dnsmasq log does not contain expected DHCP sequence: diff (-got +want):\n%s", diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
handle.Close()
|
|
||||||
<-closed
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
package integration_test
|
package integration_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"router7/internal/dhcp6"
|
"router7/internal/dhcp6"
|
||||||
|
"router7/internal/testing/dnsmasq"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var v6AddrRe = regexp.MustCompile(`2001:db8::[^ ]+`)
|
||||||
|
|
||||||
func TestDHCPv6(t *testing.T) {
|
func TestDHCPv6(t *testing.T) {
|
||||||
const ns = "ns0" // name of the network namespace to use for this test
|
const ns = "ns0" // name of the network namespace to use for this test
|
||||||
|
|
||||||
@ -43,57 +43,10 @@ func TestDHCPv6(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ready, err := ioutil.TempFile("", "router7")
|
dnsmasq := dnsmasq.Run(t)
|
||||||
if err != nil {
|
defer dnsmasq.Kill()
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
ready.Close() // dnsmasq will re-create the file anyway
|
|
||||||
defer os.Remove(ready.Name()) // dnsmasq does not clean up its pid file
|
|
||||||
|
|
||||||
dnsmasq := exec.Command("ip", "netns", "exec", ns, "dnsmasq",
|
// f, err := os.Create("/tmp/pcap6")
|
||||||
"--keep-in-foreground", // cannot use --no-daemon because we need --pid-file
|
|
||||||
"--log-facility=-", // log to stderr
|
|
||||||
"--pid-file="+ready.Name(),
|
|
||||||
"--bind-interfaces",
|
|
||||||
"--interface=veth0b",
|
|
||||||
"--dhcp-range=192.168.23.2,192.168.23.10",
|
|
||||||
"--dhcp-range=::1,::10,constructor:veth0b",
|
|
||||||
"--dhcp-authoritative", // eliminate timeouts
|
|
||||||
"--no-ping", // disable ICMP confirmation of unused addresses to eliminate tedious timeout
|
|
||||||
"--leasefile-ro", // do not create a lease database
|
|
||||||
)
|
|
||||||
dnsmasq.Stdout = os.Stdout
|
|
||||||
dnsmasq.Stderr = os.Stderr
|
|
||||||
if err := dnsmasq.Start(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
done := false // TODO: fix data race
|
|
||||||
go func() {
|
|
||||||
err := dnsmasq.Wait()
|
|
||||||
if !done {
|
|
||||||
t.Fatalf("dnsmasq exited prematurely: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
defer func() {
|
|
||||||
done = true
|
|
||||||
dnsmasq.Process.Kill()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// TODO(later): use inotify instead of polling
|
|
||||||
// Wait for dnsmasq to write its process id, at which point it is already
|
|
||||||
// listening for requests.
|
|
||||||
for {
|
|
||||||
b, err := ioutil.ReadFile(ready.Name())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(string(b)) == strconv.Itoa(dnsmasq.Process.Pid) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
// f, err := os.Create("/tmp/pcap")
|
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// t.Fatal(err)
|
// t.Fatal(err)
|
||||||
// }
|
// }
|
||||||
@ -110,19 +63,26 @@ func TestDHCPv6(t *testing.T) {
|
|||||||
// closed := make(chan struct{})
|
// closed := make(chan struct{})
|
||||||
// go func() {
|
// go func() {
|
||||||
// for packet := range pkgsrc.Packets() {
|
// for packet := range pkgsrc.Packets() {
|
||||||
// if packet.Layer(layers.LayerTypeDHCPv4) != nil {
|
// //if packet.Layer(layers.LayerTypeDHCPv6) != nil {
|
||||||
// log.Printf("packet: %+v", packet)
|
// fmt.Printf("packet: %+v\n", packet)
|
||||||
// if err := pcapw.WritePacket(packet.Metadata().CaptureInfo, packet.Data()); err != nil {
|
// if err := pcapw.WritePacket(packet.Metadata().CaptureInfo, packet.Data()); err != nil {
|
||||||
// t.Fatalf("pcap.WritePacket(): %v", err)
|
// t.Fatalf("pcap.WritePacket(): %v", err)
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
|
// //}
|
||||||
// }
|
// }
|
||||||
// close(closed)
|
// close(closed)
|
||||||
// }()
|
// }()
|
||||||
// // TODO: test the capture daemon
|
// // TODO: test the capture daemon
|
||||||
|
// defer func() {
|
||||||
|
// time.Sleep(1 * time.Second)
|
||||||
|
// handle.Close()
|
||||||
|
// <-closed
|
||||||
|
// }()
|
||||||
|
|
||||||
|
duid := []byte{0x00, 0x0a, 0x00, 0x03, 0x00, 0x01, 0x4c, 0x5e, 0xc, 0x41, 0xbf, 0x39}
|
||||||
c, err := dhcp6.NewClient(dhcp6.ClientConfig{
|
c, err := dhcp6.NewClient(dhcp6.ClientConfig{
|
||||||
InterfaceName: "veth0a",
|
InterfaceName: "veth0a",
|
||||||
|
DUID: duid,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -139,7 +99,19 @@ func TestDHCPv6(t *testing.T) {
|
|||||||
t.Fatalf("unexpected config: diff (-got +want):\n%s", diff)
|
t.Fatalf("unexpected config: diff (-got +want):\n%s", diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
// time.Sleep(1 * time.Second)
|
{
|
||||||
// handle.Close()
|
got := dnsmasq.Actions()
|
||||||
// <-closed
|
want := []string{
|
||||||
|
"DHCPSOLICIT(veth0b) 00:0a:00:03:00:01:4c:5e:0c:41:bf:39",
|
||||||
|
"DHCPADVERTISE(veth0b) 2001:db8::c 00:0a:00:03:00:01:4c:5e:0c:41:bf:39",
|
||||||
|
"DHCPREQUEST(veth0b) 00:0a:00:03:00:01:4c:5e:0c:41:bf:39",
|
||||||
|
"DHCPREPLY(veth0b) 2001:db8::c 00:0a:00:03:00:01:4c:5e:0c:41:bf:39",
|
||||||
|
}
|
||||||
|
withoutMac := func(line string) string {
|
||||||
|
return v6AddrRe.ReplaceAllString(strings.TrimSpace(line), "")
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(got, want, cmp.Transformer("WithoutMAC", withoutMac)); diff != "" {
|
||||||
|
t.Errorf("dnsmasq log does not contain expected DHCP sequence: diff (-got +want):\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
147
internal/testing/dnsmasq/dnsmasq.go
Normal file
147
internal/testing/dnsmasq/dnsmasq.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
// Package dnsmasq manages the process lifecycle of the dnsmasq(8) DHCP server.
|
||||||
|
package dnsmasq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Process is a handle for a dnsmasq(8) process.
|
||||||
|
type Process struct {
|
||||||
|
killed bool // whether Kill was called
|
||||||
|
done chan struct{} // closed when Done() is called
|
||||||
|
wait chan struct{} // closed when Wait() returns
|
||||||
|
dnsmasq *exec.Cmd
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
actions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var dhcpActionRe = regexp.MustCompile(` (DHCP[^(]+\(.*)$`)
|
||||||
|
|
||||||
|
// Run starts a dnsmasq(8) process and returns a handle to it.
|
||||||
|
func Run(t *testing.T, args ...string) *Process {
|
||||||
|
const ns = "ns0" // TODO: configurable?
|
||||||
|
|
||||||
|
ready, err := ioutil.TempFile("", "router7")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ready.Close() // dnsmasq will re-create the file anyway
|
||||||
|
defer os.Remove(ready.Name()) // dnsmasq does not clean up its pid file
|
||||||
|
|
||||||
|
// iface, err := net.InterfaceByName("veth0a")
|
||||||
|
// if err != nil {
|
||||||
|
// t.Fatal(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
p := &Process{
|
||||||
|
wait: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.dnsmasq = exec.Command("ip", "netns", "exec", ns, "dnsmasq",
|
||||||
|
"--keep-in-foreground", // cannot use --no-daemon because we need --pid-file
|
||||||
|
"--log-facility=-", // log to stderr
|
||||||
|
"--pid-file="+ready.Name(),
|
||||||
|
"--bind-interfaces",
|
||||||
|
"--interface=veth0b",
|
||||||
|
"--dhcp-range=192.168.23.2,192.168.23.10",
|
||||||
|
"--dhcp-range=::1,::10,constructor:veth0b",
|
||||||
|
"--dhcp-authoritative", // eliminate timeouts
|
||||||
|
"--no-ping", // disable ICMP confirmation of unused addresses to eliminate tedious timeout
|
||||||
|
"--leasefile-ro", // do not create a lease database
|
||||||
|
)
|
||||||
|
|
||||||
|
p.dnsmasq.Stdout = os.Stdout
|
||||||
|
stderr := make(chan string)
|
||||||
|
r, w := io.Pipe()
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
go func() {
|
||||||
|
for scanner.Scan() {
|
||||||
|
stderr <- scanner.Text()
|
||||||
|
}
|
||||||
|
close(stderr)
|
||||||
|
}()
|
||||||
|
p.dnsmasq.Stderr = w
|
||||||
|
//mac := iface.HardwareAddr.String()
|
||||||
|
go func() {
|
||||||
|
for line := range stderr {
|
||||||
|
fmt.Printf("dnsmasq log line: %s\n", line)
|
||||||
|
if !strings.HasPrefix(line, "dnsmasq-dhcp") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if !strings.Contains(line, mac) {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
matches := dhcpActionRe.FindStringSubmatch(line)
|
||||||
|
if matches == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.mu.Lock()
|
||||||
|
p.actions = append(p.actions, matches[1])
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := p.dnsmasq.Start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.done = make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
err := p.dnsmasq.Wait()
|
||||||
|
close(p.wait)
|
||||||
|
select {
|
||||||
|
case <-p.done:
|
||||||
|
return // test done, any errors are from our Kill()
|
||||||
|
default:
|
||||||
|
t.Fatalf("dnsmasq exited prematurely: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// TODO(later): use inotify instead of polling
|
||||||
|
// Wait for dnsmasq to write its process id, at which point it is already
|
||||||
|
// listening for requests.
|
||||||
|
for {
|
||||||
|
b, err := ioutil.ReadFile(ready.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) == strconv.Itoa(p.dnsmasq.Process.Pid) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill shuts down the dnsmasq(8) process and returns once waitpid returns.
|
||||||
|
func (p *Process) Kill() {
|
||||||
|
if p.killed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.killed = true
|
||||||
|
close(p.done)
|
||||||
|
p.dnsmasq.Process.Kill()
|
||||||
|
<-p.wait
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions returns a string slice of dnsmasq(8) actions (as per its stderr log)
|
||||||
|
// received up until now. Use Kill before calling Actions to force a log flush.
|
||||||
|
func (p *Process) Actions() []string {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
result := make([]string, len(p.actions))
|
||||||
|
copy(result, p.actions)
|
||||||
|
return result
|
||||||
|
}
|
36
internal/testing/dnsmasq/example_test.go
Normal file
36
internal/testing/dnsmasq/example_test.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package dnsmasq_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"router7/internal/testing/dnsmasq"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example(t *testing.T) {
|
||||||
|
dnsmasq := dnsmasq.Run(t)
|
||||||
|
defer dnsmasq.Kill()
|
||||||
|
// test code here
|
||||||
|
|
||||||
|
// optionally introspect the dnsmasq log:
|
||||||
|
dnsmasq.Kill()
|
||||||
|
got := dnsmasq.Actions()
|
||||||
|
want := []string{
|
||||||
|
"DHCPDISCOVER(veth0b)",
|
||||||
|
"DHCPOFFER(veth0b)",
|
||||||
|
"DHCPREQUEST(veth0b)",
|
||||||
|
"DHCPACK(veth0b)",
|
||||||
|
"DHCPRELEASE(veth0b)",
|
||||||
|
}
|
||||||
|
actionOnly := func(line string) string {
|
||||||
|
result := line
|
||||||
|
if idx := strings.Index(result, " "); idx > -1 {
|
||||||
|
return result[:idx]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(got, want, cmp.Transformer("ActionOnly", actionOnly)); diff != "" {
|
||||||
|
t.Errorf("dnsmasq log does not contain expected DHCP sequence: diff (-got +want):\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user