219 lines
5.3 KiB
Go
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"))
|
|
})
|
|
}
|