mirror of
https://github.com/v2fly/domain-list-community.git
synced 2026-08-27 13:40:16 +03:00
Add unit tests covering the whole root package
Co-authored-by: Loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com>
This commit is contained in:
parent
646bf25ac9
commit
4642ee5d9a
574
main_test.go
574
main_test.go
@ -1,13 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/v2fly/domain-list-community/internal/dlc"
|
||||
router "github.com/v2fly/v2ray-core/v5/app/router/routercommon"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// mainExitEnv marks the re-executed test binary that has to run main().
|
||||
const mainExitEnv = "DLC_TEST_MAIN_EXIT"
|
||||
|
||||
func TestParseEntry(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@ -29,6 +38,10 @@ func TestParseEntry(t *testing.T) {
|
||||
{name: "empty rule", typ: "domain", rule: " ", wantErr: true},
|
||||
{name: "unknown type", typ: "prefix", rule: "example.com", wantErr: true},
|
||||
{name: "invalid domain", typ: "domain", rule: "exa_mple.com @ads", wantErr: true},
|
||||
{name: "invalid keyword", typ: "keyword", rule: "ad_s", wantErr: true},
|
||||
{name: "overlong domain", typ: "domain", rule: strings.Repeat("a.", 127) + "com", wantErr: true},
|
||||
{name: "invalid attr", typ: "domain", rule: "example.com @a_ds", wantErr: true},
|
||||
{name: "invalid affiliation", typ: "domain", rule: "example.com &other_list", wantErr: true},
|
||||
{name: "empty label", typ: "domain", rule: "example..com", wantErr: true},
|
||||
{name: "trailing dot", typ: "full", rule: "example.com.", wantErr: true},
|
||||
{name: "leading dot", typ: "domain", rule: ".example.com", wantErr: true},
|
||||
@ -81,6 +94,7 @@ func TestParseInclusion(t *testing.T) {
|
||||
{name: "empty ban attr", rule: "other @-", wantErr: true},
|
||||
{name: "empty rule", rule: " ", wantErr: true},
|
||||
{name: "invalid name", rule: "other@list", wantErr: true},
|
||||
{name: "unknown field", rule: "other list", wantErr: true},
|
||||
{name: "affiliation", rule: "other &another", wantErr: true},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
@ -193,6 +207,566 @@ func TestResolveCircularInclusion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateChars(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
validate func(string) bool
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{name: "domain chars", validate: validateDomainChars, input: "my-example.com", want: true},
|
||||
{name: "empty domain chars", validate: validateDomainChars, input: "", want: false},
|
||||
{name: "uppercase domain chars", validate: validateDomainChars, input: "Example.com", want: false},
|
||||
{name: "domain name", validate: validateDomainName, input: "a.example.com", want: true},
|
||||
{name: "empty domain name", validate: validateDomainName, input: "", want: false},
|
||||
{name: "attr chars", validate: validateAttrChars, input: "!ads1", want: true},
|
||||
{name: "empty attr chars", validate: validateAttrChars, input: "", want: false},
|
||||
{name: "uppercase attr chars", validate: validateAttrChars, input: "Ads", want: false},
|
||||
{name: "site name", validate: validateSiteName, input: "GEOLOCATION-!CN", want: true},
|
||||
{name: "empty site name", validate: validateSiteName, input: "", want: false},
|
||||
{name: "lowercase site name", validate: validateSiteName, input: "cn", want: false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := tc.validate(tc.input); got != tc.want {
|
||||
t.Errorf("validate(%q) = %v, want %v", tc.input, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadData(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
content := strings.Join([]string{
|
||||
"# A comment line",
|
||||
"",
|
||||
" ",
|
||||
"example.com # Rule without type falls back to domain",
|
||||
"full:mail.example.com @cn &affiliated",
|
||||
"include:other @ads",
|
||||
"keyword:example",
|
||||
`regexp:^ads\.example\.com$`,
|
||||
}, "\n")
|
||||
writeTestData(t, dataPath, map[string]string{"test": content})
|
||||
|
||||
processor := &Processor{parsedListByName: make(map[string]*ParsedList)}
|
||||
if err := processor.loadData("TEST", filepath.Join(dataPath, "test")); err != nil {
|
||||
t.Fatalf("loadData(\"TEST\") got unexpected error: %v", err)
|
||||
}
|
||||
pl := processor.parsedListByName["TEST"]
|
||||
assertPlains(t, "TEST", pl.Entries, []string{
|
||||
"domain:example.com",
|
||||
"full:mail.example.com:@cn",
|
||||
"keyword:example",
|
||||
`regexp:^ads\.example\.com$`,
|
||||
})
|
||||
if len(pl.Inclusions) != 1 || pl.Inclusions[0].Source != "OTHER" {
|
||||
t.Errorf("TEST inclusions = %+v, want one inclusion of %q", pl.Inclusions, "OTHER")
|
||||
}
|
||||
// Affiliated entries are also added to the affiliated list
|
||||
apl, exist := processor.parsedListByName["AFFILIATED"]
|
||||
if !exist {
|
||||
t.Fatal("list \"AFFILIATED\" does not exist")
|
||||
}
|
||||
assertPlains(t, "AFFILIATED", apl.Entries, []string{"full:mail.example.com:@cn"})
|
||||
}
|
||||
|
||||
func TestLoadDataErrors(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
files := map[string]string{
|
||||
"invalid-entry": "domain:exa_mple.com\n",
|
||||
"invalid-inclusion": "include:other@list\n",
|
||||
}
|
||||
writeTestData(t, dataPath, files)
|
||||
for name := range files {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
processor := &Processor{parsedListByName: make(map[string]*ParsedList)}
|
||||
if err := processor.loadData("TEST", filepath.Join(dataPath, name)); err == nil {
|
||||
t.Errorf("loadData(%q) = nil, want parsing error", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Run("missing file", func(t *testing.T) {
|
||||
processor := &Processor{parsedListByName: make(map[string]*ParsedList)}
|
||||
if err := processor.loadData("TEST", filepath.Join(dataPath, "missing")); err == nil {
|
||||
t.Error("loadData(\"missing\") = nil, want file opening error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestResolveMissingList(t *testing.T) {
|
||||
processor := &Processor{parsedListByName: make(map[string]*ParsedList)}
|
||||
if _, err := processor.resolveList("MISSING"); err == nil {
|
||||
t.Error("resolveList(\"MISSING\") = nil, want list not found error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveEmptyList(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
writeTestData(t, dataPath, map[string]string{"empty": "# Only a comment\n"})
|
||||
processor := &Processor{parsedListByName: make(map[string]*ParsedList)}
|
||||
if err := processor.loadData("EMPTY", filepath.Join(dataPath, "empty")); err != nil {
|
||||
t.Fatalf("loadData(\"EMPTY\") got unexpected error: %v", err)
|
||||
}
|
||||
pl, err := processor.resolveList("EMPTY")
|
||||
if err != nil {
|
||||
t.Fatalf("resolveList(\"EMPTY\") got unexpected error: %v", err)
|
||||
}
|
||||
if len(pl.FinalEntries) != 0 {
|
||||
t.Errorf("EMPTY final entries = %v, want none", pl.FinalEntries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeProtoList(t *testing.T) {
|
||||
rules := []struct{ typ, rule string }{
|
||||
{"domain", "example.com @cn @ads"},
|
||||
{"full", "www.example.com"},
|
||||
{"keyword", "example"},
|
||||
{"regexp", `^example\.com$`},
|
||||
}
|
||||
entries := make([]*Entry, 0, len(rules))
|
||||
for _, r := range rules {
|
||||
entry, _, err := parseEntry(r.typ, r.rule)
|
||||
if err != nil {
|
||||
t.Fatalf("parseEntry(%q, %q) got unexpected error: %v", r.typ, r.rule, err)
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
site := makeProtoList("TEST", entries)
|
||||
if site.CountryCode != "TEST" {
|
||||
t.Errorf("makeProtoList() country code = %q, want %q", site.CountryCode, "TEST")
|
||||
}
|
||||
wantTypes := []router.Domain_Type{
|
||||
router.Domain_RootDomain,
|
||||
router.Domain_Full,
|
||||
router.Domain_Plain,
|
||||
router.Domain_Regex,
|
||||
}
|
||||
if len(site.Domain) != len(wantTypes) {
|
||||
t.Fatalf("makeProtoList() domains = %v, want %d domains", site.Domain, len(wantTypes))
|
||||
}
|
||||
for i, pdomain := range site.Domain {
|
||||
if pdomain.Type != wantTypes[i] {
|
||||
t.Errorf("makeProtoList() domain[%d] type = %v, want %v", i, pdomain.Type, wantTypes[i])
|
||||
}
|
||||
if pdomain.Value != entries[i].Value {
|
||||
t.Errorf("makeProtoList() domain[%d] value = %q, want %q", i, pdomain.Value, entries[i].Value)
|
||||
}
|
||||
}
|
||||
attrs := site.Domain[0].Attribute
|
||||
if len(attrs) != 2 || attrs[0].Key != "ads" || attrs[1].Key != "cn" {
|
||||
t.Fatalf("makeProtoList() domain[0] attributes = %v, want %v", attrs, []string{"ads", "cn"})
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
if !attr.GetBoolValue() {
|
||||
t.Errorf("makeProtoList() attribute %q value = %v, want true", attr.Key, attr.GetBoolValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadTasks(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
testCases := []struct {
|
||||
name string
|
||||
content string
|
||||
want []DatTask
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
content: `[{"name":"dlc.dat","mode":"all"},{"name":"cn.dat","mode":"allowlist","lists":["cn"]},{"name":"nocn.dat","mode":"denylist","lists":["cn"]}]`,
|
||||
want: []DatTask{
|
||||
{Name: "dlc.dat", Mode: ModeAll},
|
||||
{Name: "cn.dat", Mode: ModeAllowlist, Lists: []string{"cn"}},
|
||||
{Name: "nocn.dat", Mode: ModeDenylist, Lists: []string{"cn"}},
|
||||
},
|
||||
},
|
||||
{name: "invalid json", content: "{", wantErr: true},
|
||||
{name: "missing name", content: `[{"mode":"all"}]`, wantErr: true},
|
||||
{name: "invalid mode", content: `[{"name":"dlc.dat","mode":"unknown"}]`, wantErr: true},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
path := filepath.Join(dataPath, tc.name+".json")
|
||||
if err := os.WriteFile(path, []byte(tc.content), 0644); err != nil {
|
||||
t.Fatalf("failed to write test profile %q: %v", path, err)
|
||||
}
|
||||
tasks, err := loadTasks(path)
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("loadTasks(%q) = %+v, want error", tc.name, tasks)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("loadTasks(%q) got unexpected error: %v", tc.name, err)
|
||||
}
|
||||
if !slices.EqualFunc(tasks, tc.want, func(a, b DatTask) bool {
|
||||
return a.Name == b.Name && a.Mode == b.Mode && slices.Equal(a.Lists, b.Lists)
|
||||
}) {
|
||||
t.Errorf("loadTasks(%q) = %+v, want %+v", tc.name, tasks, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Run("missing file", func(t *testing.T) {
|
||||
if _, err := loadTasks(filepath.Join(dataPath, "missing.json")); err == nil {
|
||||
t.Error("loadTasks(\"missing.json\") = nil, want file opening error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAssembleDat(t *testing.T) {
|
||||
outPath := t.TempDir()
|
||||
setFlag(t, outputDir, outPath)
|
||||
gs := newTestGeoSites("APPLE", "CN", "GOOGLE")
|
||||
testCases := []struct {
|
||||
name string
|
||||
task DatTask
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "all", task: DatTask{Name: "All.dat", Mode: ModeAll}, want: []string{"APPLE", "CN", "GOOGLE"}},
|
||||
{name: "allowlist", task: DatTask{Name: "allow.dat", Mode: ModeAllowlist, Lists: []string{"google", "cn", "CN"}}, want: []string{"CN", "GOOGLE"}},
|
||||
{name: "denylist", task: DatTask{Name: "deny.dat", Mode: ModeDenylist, Lists: []string{"cn", "missing"}}, want: []string{"APPLE", "GOOGLE"}},
|
||||
{name: "denylist without valid list", task: DatTask{Name: "nodeny.dat", Mode: ModeDenylist, Lists: []string{"missing"}}, want: []string{"APPLE", "CN", "GOOGLE"}},
|
||||
{name: "allowlist with missing list", task: DatTask{Name: "missing.dat", Mode: ModeAllowlist, Lists: []string{"missing"}}, wantErr: true},
|
||||
{name: "allowlist without list", task: DatTask{Name: "none.dat", Mode: ModeAllowlist}, wantErr: true},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := gs.assembleDat(tc.task)
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("assembleDat(%+v) = nil, want error", tc.task)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("assembleDat(%+v) got unexpected error: %v", tc.task, err)
|
||||
}
|
||||
got := readDatSites(t, filepath.Join(outPath, strings.ToLower(tc.task.Name)))
|
||||
if !slices.Equal(got, tc.want) {
|
||||
t.Errorf("assembleDat(%+v) = %v, want %v", tc.task, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssembleDatErrors(t *testing.T) {
|
||||
outPath := t.TempDir()
|
||||
setFlag(t, outputDir, outPath)
|
||||
// A directory occupying the output path makes writing the dat file fail
|
||||
if err := os.Mkdir(filepath.Join(outPath, "blocked.dat"), 0755); err != nil {
|
||||
t.Fatalf("failed to create blocking directory: %v", err)
|
||||
}
|
||||
gs := newTestGeoSites("CN")
|
||||
if err := gs.assembleDat(DatTask{Name: "Blocked.dat", Mode: ModeAll}); err == nil {
|
||||
t.Error("assembleDat() = nil, want file writing error")
|
||||
}
|
||||
// Invalid UTF-8 in a proto3 string field makes marshaling fail
|
||||
invalid := &GeoSites{Sites: []*router.GeoSite{{CountryCode: "\xff"}}}
|
||||
if err := invalid.assembleDat(DatTask{Name: "invalid.dat", Mode: ModeAll}); err == nil {
|
||||
t.Error("assembleDat() = nil, want marshaling error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWritePlainList(t *testing.T) {
|
||||
outPath := t.TempDir()
|
||||
setFlag(t, outputDir, outPath)
|
||||
entries := []*Entry{{Plain: "domain:example.com"}, {Plain: "full:www.example.com:@ads"}}
|
||||
if err := writePlainList("CN", entries); err != nil {
|
||||
t.Fatalf("writePlainList(\"CN\") got unexpected error: %v", err)
|
||||
}
|
||||
got, err := os.ReadFile(filepath.Join(outPath, "cn.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read generated list: %v", err)
|
||||
}
|
||||
want := "domain:example.com\nfull:www.example.com:@ads\n"
|
||||
if string(got) != want {
|
||||
t.Errorf("writePlainList(\"CN\") wrote %q, want %q", got, want)
|
||||
}
|
||||
// A directory occupying the output path makes creating the list file fail
|
||||
if err := os.Mkdir(filepath.Join(outPath, "blocked.txt"), 0755); err != nil {
|
||||
t.Fatalf("failed to create blocking directory: %v", err)
|
||||
}
|
||||
if err := writePlainList("Blocked", entries); err == nil {
|
||||
t.Error("writePlainList(\"Blocked\") = nil, want file creating error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
outPath := filepath.Join(t.TempDir(), "output") // Not existing yet
|
||||
writeTestData(t, dataPath, map[string]string{
|
||||
"cn": "# Chinese sites\ndomain:example.cn\nfull:www.example.cn @ads\nkeyword:examplecn\n",
|
||||
"apple": "include:cn\ndomain:apple.com &icloud\n",
|
||||
"icloud": "domain:icloud.com\n",
|
||||
"empty": "# Nothing here\n",
|
||||
})
|
||||
setRunFlags(t, dataPath, outPath, "test.dat", "", "cn, ,missing,")
|
||||
|
||||
if err := run(); err != nil {
|
||||
t.Fatalf("run() got unexpected error: %v", err)
|
||||
}
|
||||
got := readDatLists(t, filepath.Join(outPath, "test.dat"))
|
||||
want := map[string][]string{
|
||||
"APPLE": {"domain:apple.com", "domain:example.cn", "full:www.example.cn:@ads", "keyword:examplecn"},
|
||||
"CN": {"domain:example.cn", "full:www.example.cn:@ads", "keyword:examplecn"},
|
||||
"ICLOUD": {"domain:apple.com", "domain:icloud.com"},
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("run() generated lists %v, want %v", got, want)
|
||||
}
|
||||
for name, wantRules := range want {
|
||||
if !slices.Equal(got[name], wantRules) {
|
||||
t.Errorf("run() list %q = %v, want %v", name, got[name], wantRules)
|
||||
}
|
||||
}
|
||||
plain, err := os.ReadFile(filepath.Join(outPath, "cn.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read exported list: %v", err)
|
||||
}
|
||||
wantPlain := "domain:example.cn\nfull:www.example.cn:@ads\nkeyword:examplecn\n"
|
||||
if string(plain) != wantPlain {
|
||||
t.Errorf("run() exported list = %q, want %q", plain, wantPlain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunWithDatProfile(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
outPath := t.TempDir()
|
||||
writeTestData(t, dataPath, map[string]string{
|
||||
"cn": "domain:example.cn\n",
|
||||
"google": "domain:google.com\n",
|
||||
})
|
||||
profile := filepath.Join(t.TempDir(), "profile.json")
|
||||
if err := os.WriteFile(profile, []byte(`[{"name":"CN.dat","mode":"allowlist","lists":["cn"]}]`), 0644); err != nil {
|
||||
t.Fatalf("failed to write test profile: %v", err)
|
||||
}
|
||||
setRunFlags(t, dataPath, outPath, "dlc.dat", profile, "")
|
||||
|
||||
if err := run(); err != nil {
|
||||
t.Fatalf("run() got unexpected error: %v", err)
|
||||
}
|
||||
if got, want := readDatSites(t, filepath.Join(outPath, "cn.dat")), []string{"CN"}; !slices.Equal(got, want) {
|
||||
t.Errorf("run() generated dat = %v, want %v", got, want)
|
||||
}
|
||||
// Only the dat files defined by the profile are generated
|
||||
if _, err := os.Stat(filepath.Join(outPath, "dlc.dat")); !os.IsNotExist(err) {
|
||||
t.Errorf("run() generated %q, want it not to exist", "dlc.dat")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunErrors(t *testing.T) {
|
||||
t.Run("missing data directory", func(t *testing.T) {
|
||||
setRunFlags(t, filepath.Join(t.TempDir(), "missing"), t.TempDir(), "dlc.dat", "", "")
|
||||
if err := run(); err == nil {
|
||||
t.Error("run() = nil, want data directory error")
|
||||
}
|
||||
})
|
||||
t.Run("invalid list name", func(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
writeTestData(t, dataPath, map[string]string{"invalid_name": "domain:example.com\n"})
|
||||
setRunFlags(t, dataPath, t.TempDir(), "dlc.dat", "", "")
|
||||
if err := run(); err == nil {
|
||||
t.Error("run() = nil, want invalid list name error")
|
||||
}
|
||||
})
|
||||
t.Run("invalid rule", func(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
writeTestData(t, dataPath, map[string]string{"cn": "domain:exa_mple.cn\n"})
|
||||
setRunFlags(t, dataPath, t.TempDir(), "dlc.dat", "", "")
|
||||
if err := run(); err == nil {
|
||||
t.Error("run() = nil, want invalid rule error")
|
||||
}
|
||||
})
|
||||
t.Run("circular inclusion", func(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
writeTestData(t, dataPath, map[string]string{
|
||||
"first": "include:second\n",
|
||||
"second": "include:first\n",
|
||||
})
|
||||
setRunFlags(t, dataPath, t.TempDir(), "dlc.dat", "", "")
|
||||
if err := run(); err == nil {
|
||||
t.Error("run() = nil, want circular inclusion error")
|
||||
}
|
||||
})
|
||||
t.Run("invalid output directory", func(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
writeTestData(t, dataPath, map[string]string{"cn": "domain:example.cn\n"})
|
||||
outPath := t.TempDir()
|
||||
blocking := filepath.Join(outPath, "blocking")
|
||||
if err := os.WriteFile(blocking, nil, 0644); err != nil {
|
||||
t.Fatalf("failed to create blocking file: %v", err)
|
||||
}
|
||||
setRunFlags(t, dataPath, filepath.Join(blocking, "output"), "dlc.dat", "", "")
|
||||
if err := run(); err == nil {
|
||||
t.Error("run() = nil, want output directory error")
|
||||
}
|
||||
})
|
||||
t.Run("missing dat profile", func(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
writeTestData(t, dataPath, map[string]string{"cn": "domain:example.cn\n"})
|
||||
setRunFlags(t, dataPath, t.TempDir(), "dlc.dat", filepath.Join(t.TempDir(), "missing.json"), "")
|
||||
if err := run(); err == nil {
|
||||
t.Error("run() = nil, want dat profile error")
|
||||
}
|
||||
})
|
||||
t.Run("failed plaintext export", func(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
writeTestData(t, dataPath, map[string]string{"cn": "domain:example.cn\n"})
|
||||
outPath := t.TempDir()
|
||||
if err := os.Mkdir(filepath.Join(outPath, "cn.txt"), 0755); err != nil {
|
||||
t.Fatalf("failed to create blocking directory: %v", err)
|
||||
}
|
||||
setRunFlags(t, dataPath, outPath, "dlc.dat", "", "cn")
|
||||
if err := run(); err == nil {
|
||||
t.Error("run() = nil, want plaintext export error")
|
||||
}
|
||||
})
|
||||
t.Run("failed dat task", func(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
writeTestData(t, dataPath, map[string]string{"cn": "domain:example.cn\n"})
|
||||
profile := filepath.Join(t.TempDir(), "profile.json")
|
||||
if err := os.WriteFile(profile, []byte(`[{"name":"none.dat","mode":"allowlist","lists":["missing"]}]`), 0644); err != nil {
|
||||
t.Fatalf("failed to write test profile: %v", err)
|
||||
}
|
||||
setRunFlags(t, dataPath, t.TempDir(), "dlc.dat", profile, "")
|
||||
if err := run(); err == nil {
|
||||
t.Error("run() = nil, want dat task error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMainGeneratesDat(t *testing.T) {
|
||||
dataPath := t.TempDir()
|
||||
outPath := t.TempDir()
|
||||
writeTestData(t, dataPath, map[string]string{"cn": "domain:example.cn\n"})
|
||||
setRunFlags(t, dataPath, outPath, "dlc.dat", "", "")
|
||||
|
||||
oldArgs := os.Args
|
||||
t.Cleanup(func() { os.Args = oldArgs })
|
||||
os.Args = []string{"domain-list-community", "--datapath=" + dataPath, "--outputdir=" + outPath, "--outputname=main.dat"}
|
||||
main()
|
||||
|
||||
if got, want := readDatSites(t, filepath.Join(outPath, "main.dat")), []string{"CN"}; !slices.Equal(got, want) {
|
||||
t.Errorf("main() generated dat = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMainExitsOnError re-executes the test binary, because main() terminates
|
||||
// the process when the generation fails.
|
||||
func TestMainExitsOnError(t *testing.T) {
|
||||
if os.Getenv(mainExitEnv) == "1" {
|
||||
os.Args = []string{"domain-list-community", "--datapath=" + filepath.Join(t.TempDir(), "missing")}
|
||||
main()
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(os.Args[0], "-test.run=^TestMainExitsOnError$")
|
||||
cmd.Env = append(os.Environ(), mainExitEnv+"=1")
|
||||
out, err := cmd.CombinedOutput()
|
||||
var exitErr *exec.ExitError
|
||||
if !errors.As(err, &exitErr) || exitErr.ExitCode() != 1 {
|
||||
t.Fatalf("main() exited with %v, want exit status 1, output:\n%s", err, out)
|
||||
}
|
||||
if !strings.Contains(string(out), "[Fatal]") {
|
||||
t.Errorf("main() output = %q, want it to report a fatal error", out)
|
||||
}
|
||||
}
|
||||
|
||||
// setFlag overrides a command line flag for the duration of the test.
|
||||
func setFlag(t *testing.T, flagValue *string, value string) {
|
||||
t.Helper()
|
||||
old := *flagValue
|
||||
*flagValue = value
|
||||
t.Cleanup(func() { *flagValue = old })
|
||||
}
|
||||
|
||||
func setRunFlags(t *testing.T, data, output, name, profile, exports string) {
|
||||
t.Helper()
|
||||
setFlag(t, dataPath, data)
|
||||
setFlag(t, outputDir, output)
|
||||
setFlag(t, outputName, name)
|
||||
setFlag(t, datProfile, profile)
|
||||
setFlag(t, exportLists, exports)
|
||||
}
|
||||
|
||||
func writeTestData(t *testing.T, dataPath string, files map[string]string) {
|
||||
t.Helper()
|
||||
for name, content := range files {
|
||||
if err := os.WriteFile(filepath.Join(dataPath, name), []byte(content), 0644); err != nil {
|
||||
t.Fatalf("failed to write test data %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTestGeoSites(names ...string) *GeoSites {
|
||||
gs := &GeoSites{
|
||||
Sites: make([]*router.GeoSite, 0, len(names)),
|
||||
SiteIdx: make(map[string]int, len(names)),
|
||||
}
|
||||
for i, name := range names {
|
||||
gs.Sites = append(gs.Sites, &router.GeoSite{
|
||||
CountryCode: name,
|
||||
Domain: []*router.Domain{{Type: router.Domain_RootDomain, Value: strings.ToLower(name) + ".com"}},
|
||||
})
|
||||
gs.SiteIdx[name] = i
|
||||
}
|
||||
return gs
|
||||
}
|
||||
|
||||
func readDatSites(t *testing.T, path string) []string {
|
||||
t.Helper()
|
||||
lists := readDatLists(t, path)
|
||||
names := make([]string, 0, len(lists))
|
||||
for name := range lists {
|
||||
names = append(names, name)
|
||||
}
|
||||
slices.Sort(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func readDatLists(t *testing.T, path string) map[string][]string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read generated dat: %v", err)
|
||||
}
|
||||
geoSiteList := new(router.GeoSiteList)
|
||||
if err := proto.Unmarshal(data, geoSiteList); err != nil {
|
||||
t.Fatalf("failed to unmarshal generated dat: %v", err)
|
||||
}
|
||||
lists := make(map[string][]string, len(geoSiteList.Entry))
|
||||
for _, site := range geoSiteList.Entry {
|
||||
rules := make([]string, 0, len(site.Domain))
|
||||
for _, pdomain := range site.Domain {
|
||||
rule := pdomain.Value
|
||||
for i, attr := range pdomain.Attribute {
|
||||
if i == 0 {
|
||||
rule += ":"
|
||||
} else {
|
||||
rule += ","
|
||||
}
|
||||
rule += "@" + attr.Key
|
||||
}
|
||||
switch pdomain.Type {
|
||||
case router.Domain_RootDomain:
|
||||
rule = dlc.RuleTypeDomain + ":" + rule
|
||||
case router.Domain_Full:
|
||||
rule = dlc.RuleTypeFullDomain + ":" + rule
|
||||
case router.Domain_Plain:
|
||||
rule = dlc.RuleTypeKeyword + ":" + rule
|
||||
case router.Domain_Regex:
|
||||
rule = dlc.RuleTypeRegexp + ":" + rule
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
lists[site.CountryCode] = rules
|
||||
}
|
||||
return lists
|
||||
}
|
||||
|
||||
func assertList(t *testing.T, p *Processor, name string, want []string) {
|
||||
t.Helper()
|
||||
pl, exist := p.parsedListByName[name]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user