AdGuardHome/internal/aghnet/hostscontainer_test.go
Eugene Burkov 2a546aa609 Pull request 2149: 6711 watch hosts
Updates #6711.

Squashed commit of the following:

commit 3ddfe809f76325c2d4cda0715a7bcc15e76a2388
Merge: 185957cd0 d338451fa
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Feb 13 13:01:30 2024 +0300

    Merge branch 'master' into 6711-watch-hosts

commit 185957cd01516e5955e388108615e6f131d6ad71
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 9 18:11:41 2024 +0300

    aghos: imp docs

commit 3afbbcbb7ab6cc60c7c40ef8acd5b3ddf52cb3d1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 9 15:40:02 2024 +0300

    all: upd golibs, imp fswatcher
2024-02-13 13:19:22 +03:00

219 lines
5.3 KiB
Go

package aghnet_test
import (
"net/netip"
"path"
"sync/atomic"
"testing"
"testing/fstest"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewHostsContainer(t *testing.T) {
const dirname = "dir"
const filename = "file1"
p := path.Join(dirname, filename)
testFS := fstest.MapFS{
p: &fstest.MapFile{Data: []byte("127.0.0.1 localhost")},
}
testCases := []struct {
wantErr error
name string
paths []string
}{{
wantErr: nil,
name: "one_file",
paths: []string{p},
}, {
wantErr: aghnet.ErrNoHostsPaths,
name: "no_files",
paths: []string{},
}, {
wantErr: aghnet.ErrNoHostsPaths,
name: "non-existent_file",
paths: []string{path.Join(dirname, filename+"2")},
}, {
wantErr: nil,
name: "whole_dir",
paths: []string{dirname},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
onAdd := func(name string) (err error) {
assert.Contains(t, tc.paths, name)
return nil
}
var eventsCalledCounter uint32
eventsCh := make(chan struct{})
onEvents := func() (e <-chan struct{}) {
assert.Equal(t, uint32(1), atomic.AddUint32(&eventsCalledCounter, 1))
return eventsCh
}
hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: onEvents,
OnAdd: onAdd,
OnClose: func() (err error) { return nil },
}, tc.paths...)
if tc.wantErr != nil {
require.ErrorIs(t, err, tc.wantErr)
assert.Nil(t, hc)
return
}
testutil.CleanupAndRequireSuccess(t, hc.Close)
require.NoError(t, err)
require.NotNil(t, hc)
assert.NotNil(t, <-hc.Upd())
eventsCh <- struct{}{}
assert.Equal(t, uint32(1), atomic.LoadUint32(&eventsCalledCounter))
})
}
t.Run("nil_fs", func(t *testing.T) {
require.Panics(t, func() {
_, _ = aghnet.NewHostsContainer(nil, &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
// Those shouldn't panic.
OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(name string) (err error) { return nil },
OnClose: func() (err error) { return nil },
}, p)
})
})
t.Run("nil_watcher", func(t *testing.T) {
require.Panics(t, func() {
_, _ = aghnet.NewHostsContainer(testFS, nil, p)
})
})
t.Run("err_watcher", func(t *testing.T) {
const errOnAdd errors.Error = "error"
errWatcher := &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan struct{}) { panic("not implemented") },
OnAdd: func(name string) (err error) { return errOnAdd },
OnClose: func() (err error) { return nil },
}
hc, err := aghnet.NewHostsContainer(testFS, errWatcher, p)
require.ErrorIs(t, err, errOnAdd)
assert.Nil(t, hc)
})
}
func TestHostsContainer_refresh(t *testing.T) {
// TODO(e.burkov): Test the case with no actual updates.
ip := netutil.IPv4Localhost()
ipStr := ip.String()
anotherIPStr := "1.2.3.4"
anotherIP := netip.MustParseAddr(anotherIPStr)
r1 := &hostsfile.Record{
Addr: ip,
Source: "file1",
Names: []string{"hostname"},
}
r2 := &hostsfile.Record{
Addr: anotherIP,
Source: "file2",
Names: []string{"alias"},
}
r1Data, _ := r1.MarshalText()
r2Data, _ := r2.MarshalText()
testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: r1Data}}
// event is a convenient alias for an empty struct{} to emit test events.
type event = struct{}
eventsCh := make(chan event, 1)
t.Cleanup(func() { close(eventsCh) })
w := &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan event) { return eventsCh },
OnAdd: func(name string) (err error) {
assert.Equal(t, "dir", name)
return nil
},
OnClose: func() (err error) { return nil },
}
hc, err := aghnet.NewHostsContainer(testFS, w, "dir")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, hc.Close)
strg, _ := hostsfile.NewDefaultStorage()
strg.Add(r1)
t.Run("initial_refresh", func(t *testing.T) {
upd, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
require.True(t, ok)
assert.True(t, strg.Equal(upd))
})
strg.Add(r2)
t.Run("second_refresh", func(t *testing.T) {
testFS["dir/file2"] = &fstest.MapFile{Data: r2Data}
eventsCh <- event{}
upd, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
require.True(t, ok)
assert.True(t, strg.Equal(upd))
})
t.Run("double_refresh", func(t *testing.T) {
// Make a change once.
testFS["dir/file1"] = &fstest.MapFile{Data: []byte(ipStr + " alias\n")}
eventsCh <- event{}
// Require the changes are written.
current, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
require.True(t, ok)
require.Empty(t, current.ByName("hostname"))
// Make a change again.
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + " hostname\n")}
eventsCh <- event{}
// Require the changes are written.
current, ok = testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
require.True(t, ok)
require.NotEmpty(t, current.ByName("hostname"))
})
}