mirror of
https://github.com/openwrt/packages.git
synced 2026-06-17 17:00:28 +04:00
adblock-fast: update to 1.2.2-r6
Update adblock-fast from 1.2.1-r7 to 1.2.2-r6. This is a major
architectural rewrite that ports the core business logic from a ~2,700-line
monolithic shell script (`/etc/init.d/adblock-fast`) to a ~2,850-line ucode
module (`/lib/adblock-fast/adblock-fast.uc`), reducing the init script to a
thin ~130-line procd wrapper. The rewrite also introduces a comprehensive
test suite and adds the AGPL-3.0-or-later LICENSE file.
---
- **36 files changed**, +5,787 / -2,836 lines (net +2,951)
- **1 commit**: `0263b2b` — `adblock-fast: update to 1.2.2-r6`
---
The previous implementation embedded all business logic (download pipeline,
domain processing, resolver configuration, status reporting, caching)
inside the init.d script as a ~2,700-line POSIX shell script. This made the
code difficult to test, maintain, and extend. Shell limitations (no native
data structures, reliance on subshell `eval`, global namespace pollution)
also introduced fragility and performance overhead from repeated subprocess
spawning for UCI/ubus operations.
```
/etc/init.d/adblock-fast (131 lines) — Thin procd wrapper
/lib/adblock-fast/adblock-fast.uc (2849 lines) — Core logic (ucode)
/lib/adblock-fast/cli.uc (95 lines) — CLI action dispatcher
```
The init script now delegates all operations to the ucode module via:
```sh
readonly _ucode="ucode -S -L /lib/${packageName} /lib/${packageName}/cli.uc --"
```
The CLI dispatcher (`cli.uc`) maps init script actions (start, stop,
status, allow, check, pause, etc.) to the module's exported functions.
The init script retains only procd lifecycle glue (`start_service`,
`stop_service`, `service_triggers`, `service_data`) and UCI validation
schemas.
1. **Native UCI/ubus bindings** — Direct `cursor()` and `connect()` calls
replace subprocess-heavy `uci get/set` and `jsonfilter` pipelines
2. **Proper data structures** — Objects and arrays for config, status
tracking, DNS mode definitions; no more string-concatenation state
management
3. **Streaming I/O** — 64KB chunked file reads for blocklist processing
instead of loading entire files into memory via pipes
4. **Memoized environment detection** — Platform capabilities (installed
resolvers, ipset/nftset support, downloader detection) cached on first
call
5. **Centralized trigger logic** — Config diff comparison
(`adb_config_cache()`) determines download/restart/skip in one place
6. **Testable** — Module exports enable direct unit testing without mocking
an entire init system
---
- `+ucode` — ucode interpreter runtime
- `+ucode-mod-fs` — Filesystem operations (readfile, writefile, popen,
stat, etc.)
- `+ucode-mod-uci` — Native UCI cursor API
- `+ucode-mod-ubus` — Native ubus RPC API
- `+jshn` — No longer needed (was used for JSON parsing in shell)
- URL updated from `github.com/stangri/adblock-fast/` to
`github.com/mossdef-org/adblock-fast/`
- Install target now installs `/lib/adblock-fast/adblock-fast.uc` and
`/lib/adblock-fast/cli.uc` alongside the init script
- Version stamp now patches the ucode module
(`version:` field) instead of init script (`PKG_VERSION` variable)
- `postinst` script removed (service enable handled elsewhere)
- `prerm` script simplified: only purges cache, no longer
stops service or removes rc.d symlinks (handled by procd)
---
The module supports all existing DNS resolver integrations through a
unified `dns_modes{}` configuration map. Each mode defines output file
paths, gzip cache names, sed format/parse filters, and grep patterns:
| Mode | Output Format |
|----------------------|--------------------------------------------------|
| `dnsmasq.addnhosts` | `127.0.0.1 domain` (+ `:: domain` with IPv6) |
| `dnsmasq.conf` | `local=/domain/` |
| `dnsmasq.ipset` | `ipset=/domain/adb` |
| `dnsmasq.nftset` | `nftset=/domain/4#inet#fw4#adb4[,6#...]` |
| `dnsmasq.servers` | `server=/domain/` (block) / `server=/domain/#` (allow) |
| `smartdns.domainset` | Raw domain (with smartdns conf wrapper) |
| `smartdns.ipset` | Raw domain (with smartdns ipset conf) |
| `smartdns.nftset` | Raw domain (with smartdns nftset conf) |
| `unbound.adb_list` | `local-zone: "domain." always_nxdomain` |
The download pipeline auto-detects blocklist format from content:
| Format | Detection | Example |
|--------------|-------------------------------------|----------------------------|
| AdBlock Plus | `[Adblock Plus]` header / `^||` | `\|\|example.com^` |
| dnsmasq | `^server=` | `server=/example.com/` |
| dnsmasq2 | `^local=` | `local=/example.com/` |
| dnsmasq3 | `^address=` | `address=/example.com/0.0.0.0` |
| hosts | `^0.0.0.0\s` or `^127.0.0.1\s` | `0.0.0.0 example.com` |
| domains | (fallback — plain domain list) | `example.com` |
```
For each file_url UCI section:
→ Download URL (curl with retries, timeout, optional max-file-size)
→ Auto-detect format → Apply format-specific sed filter → Extract domains
→ Append to accumulator (blocked or allowed)
Merge phase:
→ sort -u (deduplicate)
→ Subdomain optimization (awk label-reverse → sort → dedup → reverse)
→ Remove allowed domains (sed -f generated_script)
→ Inject canary domains (iCloud Private Relay, Mozilla DoH)
→ Inject manually blocked_domain entries from config
→ Format for target DNS resolver
→ Optional validity check (remove malformed entries)
→ Atomic rename to output file
Resolver phase:
→ Update resolver config (UCI: addnhosts, conf-dir, server files)
→ Sanity check (dnsmasq --test)
→ Restart resolver service
→ Heartbeat probe (resolve canary domain to verify blocking)
→ Revert on failure
```
| Function | Purpose |
|-----------------------|------------------------------------------------------|
| `start(args)` | Main lifecycle: download, restore from cache, or restart |
| `stop()` | Disable blocking, flush kernel state, cleanup |
| `status_service()` | Report status to syslog/ubus |
| `allow(domain)` | Whitelist domain in live blocklist + UCI config |
| `check(pattern)` | Search current blocklist for domain |
| `check_tld()` | Detect TLD entries (sanity check) |
| `check_leading_dot()` | Detect leading-dot errors |
| `check_lists(domain)` | Search upstream list URLs for domain |
| `dl()` | Force re-download all lists |
| `killcache()` | Purge all cached files |
| `pause(seconds)` | Temporarily disable blocking |
| `show_blocklist()` | Output parsed blocklist to stdout |
| `sizes()` | Fetch/display configured blocklist file sizes |
| `get_init_status()` | Full service state for UI/RPC clients |
| `get_init_list()` | Enabled/disabled status |
| `get_platform_support()` | Detect installed resolvers and features |
| `get_file_url_filesizes()` | Return cached/live URL metadata |
- 40+ localized message codes (e.g., `errorDownloadingList`,
`errorConfigValidationFail`, `warningSanityCheckTLD`)
- Errors/warnings accumulated in `status_data{}` arrays
- Synced atomically to ubus service data for UI consumption
- Status states: `statusSuccess`, `statusFail`, `statusDownloading`,
`statusProcessing`, `statusRestarting`, `statusPaused`
---
The init script (`/etc/init.d/adblock-fast`) is reduced from ~2,700 to ~130
lines. It now serves exclusively as a procd service wrapper:
- **procd lifecycle**: `start_service()` calls ucode `start`, captures
shell output for `service_data()`; `stop_service()` calls ucode `stop`
- **Service triggers**: WAN interface triggers, config change triggers, UCI
validation (unchanged from previous version)
- **Extra commands**: `allow`, `check`, `check_tld`, `check_leading_dot`,
`check_lists`, `dl`, `killcache`, `pause`, `show_blocklist`, `sizes`,
`version` — all delegate directly to ucode CLI dispatcher
- **procd data bridge**: `emit_procd_shell()` in ucode generates shell
statements that the init script `eval`s for `service_data()` and
`service_stopped()`/`service_started()` hooks (firewall restart flag)
---
The `90-adblock-fast` uci-defaults script is simplified from 181 to 65
lines:
- **Removed**: Entire `simple-adblock` migration path (config, cache files,
URL lists). This migration was for the initial transition from
simple-adblock to adblock-fast and is no longer needed.
- **Retained**: List name migration (adds `name` option to `file_url`
sections that lack one, using pristine default config as reference),
config key renames (`debug` → `debug_init_script`, `proc_debug` →
`debug_performance`, `sanity_check` → `dnsmasq_sanity_check`)
- **Simplified**: Uses direct `uci` commands instead of sourcing the init
script for `uci_get`/`uci_set` helpers. Pristine config lookup now
supports both apk (`.apk-new`) and opkg (`-opkg`) package manager
conventions.
---
A full test suite is added in `net/adblock-fast/tests/` (16 new files,
~1,800 lines) mock-and-expect pattern.
- **Module patching**: Converts ES6 imports to CommonJS requires, redirects
hardcoded system paths to temp directories for isolation
- **Resolver stubs**: Mock binaries for dnsmasq (v2.89), smartdns, unbound,
ipset, nft, resolveip
- **Test case format**: Markup-based (`-- Testcase --`,
`-- Environment --`, `-- Expect stdout --`, `-- File path --`) with
support for inline test data and per-test environment overrides
- **Assertion model**: Compares stdout, stderr, and exit code against
expected values using `diff -u`
- **Shell validation**: Syntax-checks init.d and uci-defaults scripts via
`sh -n`
- **Automatic cleanup**: Trap-based temp directory removal
**UCI Mock** (`tests/lib/mocklib/uci.uc`):
- Full `cursor()` interface: `load`, `get`, `get_all`, `foreach`, `set`,
`delete`, `list_add`, `list_remove`, `commit`, `changes`
- Loads JSON fixtures from `tests/mocks/uci/` (adblock-fast, dhcp, network,
smartdns, unbound configs)
- Supports `@type[index]` extended section addressing
**ubus Mock** (`tests/lib/mocklib/ubus.uc`):
- `connect()` → `call(object, method, args)` with signature-based fixture
lookup
- Fixtures in `tests/mocks/ubus/` (system info, network interface
dump/status, dnsmasq service list)
**System Call Interception** (`tests/lib/mocklib.uc`):
- Blocks service operations: `/etc/init.d/*`, `logger`, `sleep`,
`dnsmasq --test`
- Passes through data processing: `sed`, `sort`, `grep`, `awk`
- Fixed timestamp (`1615382640`) for reproducible output
- Null `getenv()` for environment isolation
**01_pipeline** — Data processing pipeline (9 tests):
1. `01_all_dns_modes` — Verifies all 9 DNS output modes produce valid,
deduplicated output (~162-165 domains from 2 input lists)
2. `02_input_format_detection` — Validates auto-detection of domains,
hosts, AdBlock Plus, and dnsmasq input formats
3. `03_subdomain_dedup` — Confirms parent domains retained, child
subdomains removed (e.g., blocks `example.com`, skips `sub.example.com`)
4. `04_allowed_domains` — Verifies `allowed_domain` config removes domains
from output while preserving others
5. `05_canary_domains` — Confirms iCloud Private Relay and Mozilla DoH
canary domain injection when enabled
6. `06_servers_mode_allow` — Validates dnsmasq.servers mode prepends
explicit allow entries (`server=/domain/#` format)
7. `07_ipv6_addnhosts` — Verifies dual-stack output (both `127.0.0.1` and
`::` entries) in addnhosts mode with IPv6 enabled
8. `08_ipv6_nftset` — Confirms nftset mode includes IPv6 set references
(`4#inet#fw4#adb4,6#inet#fw4#adb6`) when IPv6 enabled
9. `09_unbound_header` — Validates `server:` header line prepended in
unbound output mode
**02_config** — Configuration handling (1 test):
1. `01_blocked_domain_injection` — Verifies `blocked_domain` config entries
appear in output
**03_functional** — CLI command tests (2 tests):
1. `01_check_domain` — Tests `check()` correctly identifies blocked vs.
unblocked domains with appropriate output messages
2. `02_show_blocklist` — Tests `show_blocklist()` outputs parsed domain
list (162 domains, correct format)
5 curated test data files with ~160+ unique test domains across multiple
formats (plain domains, hosts, AdBlock Plus, dnsmasq), including:
- Valid tracking/ad domains for positive matching
- Overlapping domains across files for deduplication testing
- Parent/child domain pairs for subdomain optimization testing
- Invalid entries (IPs, malformed, special chars) for filter robustness
- Mock UCI/ubus fixtures simulating a standard OpenWrt environment
(512MB RAM, WAN interface up, dnsmasq running)
---
Adds the full AGPL-3.0-or-later license text (661 lines), matching the
`PKG_LICENSE` field already declared in the Makefile.
---
- Package compat bumped from `11` to `13` (in the ucode module's
`pkg.compat` constant), reflecting the architectural change
- All existing UCI configuration options preserved (same validation schema)
- All existing extra_commands preserved (same CLI interface)
- All existing DNS resolver modes preserved (same output formats)
- procd service triggers and config triggers unchanged
- `simple-adblock` migration path removed from uci-defaults (obsolete)
---
```sh
cd net/adblock-fast/tests && sh run_tests.sh
```
Requires: `ucode`, `ucode-mod-fs`, `ucode-mod-uci`, `ucode-mod-ubus`,
`sed`, `sort`, `grep`, `awk` (standard OpenWrt buildroot tools).
Signed-off-by: Stan Grishin <stangri@melmac.ca>
This commit is contained in:
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
+13
-20
@@ -4,8 +4,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=adblock-fast
|
||||
PKG_VERSION:=1.2.1
|
||||
PKG_RELEASE:=7
|
||||
PKG_VERSION:=1.2.2
|
||||
PKG_RELEASE:=6
|
||||
PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca>
|
||||
PKG_LICENSE:=AGPL-3.0-or-later
|
||||
|
||||
@@ -15,12 +15,15 @@ define Package/adblock-fast
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=AdBlock Fast Service
|
||||
URL:=https://github.com/stangri/adblock-fast/
|
||||
URL:=https://github.com/mossdef-org/adblock-fast/
|
||||
PKGARCH:=all
|
||||
DEPENDS:= \
|
||||
+jshn \
|
||||
+curl \
|
||||
+resolveip \
|
||||
+ucode \
|
||||
+ucode-mod-fs \
|
||||
+ucode-mod-uci \
|
||||
+ucode-mod-ubus \
|
||||
+!BUSYBOX_DEFAULT_AWK:gawk \
|
||||
+!BUSYBOX_DEFAULT_GREP:grep \
|
||||
+!BUSYBOX_DEFAULT_SED:sed \
|
||||
@@ -46,31 +49,21 @@ endef
|
||||
define Package/adblock-fast/install
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/etc/init.d/adblock-fast $(1)/etc/init.d/adblock-fast
|
||||
$(SED) "s|^\(readonly PKG_VERSION\).*|\1='$(PKG_VERSION)-r$(PKG_RELEASE)'|" $(1)/etc/init.d/adblock-fast
|
||||
$(INSTALL_DIR) $(1)/lib/adblock-fast
|
||||
$(INSTALL_DATA) ./files/lib/adblock-fast/adblock-fast.uc $(1)/lib/adblock-fast/adblock-fast.uc
|
||||
$(INSTALL_DATA) ./files/lib/adblock-fast/cli.uc $(1)/lib/adblock-fast/cli.uc
|
||||
$(SED) "s|^\(\tversion:\).*|\1 '$(PKG_VERSION)-r$(PKG_RELEASE)',|" $(1)/lib/adblock-fast/adblock-fast.uc
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/etc/config/adblock-fast $(1)/etc/config/adblock-fast
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_BIN) ./files/etc/uci-defaults/90-adblock-fast $(1)/etc/uci-defaults/90-adblock-fast
|
||||
endef
|
||||
|
||||
define Package/adblock-fast/postinst
|
||||
#!/bin/sh
|
||||
# check if we are on real system
|
||||
if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
/etc/init.d/adblock-fast enable
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Package/adblock-fast/prerm
|
||||
#!/bin/sh
|
||||
# check if we are on real system
|
||||
if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
echo -n "Stopping adblock-fast service... "
|
||||
{ /etc/init.d/adblock-fast stop && \
|
||||
/etc/init.d/adblock-fast killcache; } >/dev/null 2>&1 && echo "OK" || echo "FAIL"
|
||||
echo -n "Removing rc.d symlink for adblock-fast... "
|
||||
/etc/init.d/adblock-fast disable >/dev/null 2>&1 && echo "OK" || echo "FAIL"
|
||||
echo -n "Removing adblock-fast cache... "
|
||||
/etc/init.d/adblock-fast killcache >/dev/null 2>&1 && echo "OK" || echo "FAIL"
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
Executable → Regular
+40
-2653
File diff suppressed because it is too large
Load Diff
@@ -1,181 +1,65 @@
|
||||
#!/bin/sh
|
||||
# Copyright 2023 MOSSDeF, Stan Grishin (stangri@melmac.ca)
|
||||
# shellcheck disable=SC2015,SC3043,SC3060
|
||||
# Copyright 2023-2026 MOSSDeF, Stan Grishin (stangri@melmac.ca)
|
||||
# shellcheck disable=SC2015,SC3043
|
||||
|
||||
readonly pkg='adblock-fast'
|
||||
|
||||
readonly adbFunctionsFile='/etc/init.d/adblock-fast'
|
||||
if [ -s "$adbFunctionsFile" ]; then
|
||||
# shellcheck source=../../etc/init.d/adblock-fast
|
||||
. "$adbFunctionsFile"
|
||||
else
|
||||
printf "%b: adblock-fast init.d file (%s) not found! \n" '\033[0;31mERROR\033[0m' "$adbFunctionsFile"
|
||||
fi
|
||||
# ── Transition to list names ─────────────────────────────────────────
|
||||
# Adds 'name' to file_url sections that lack one, using the pristine default config
|
||||
|
||||
# Transition from simple-adblock
|
||||
_enable_url() {
|
||||
local cfg="$1" url="$2" action="$3"
|
||||
local u a
|
||||
config_get u "$cfg" 'url'
|
||||
config_get a "$cfg" 'action' 'block'
|
||||
if [ "$u" = "$url" ] && [ "$a" = "$action" ]; then
|
||||
uci_remove "$packageName" "$cfg" 'enabled' && _found=1
|
||||
fi
|
||||
}
|
||||
# Find pristine default: apk uses .apk-new, opkg uses -opkg
|
||||
pristine=''
|
||||
for f in "/etc/config/${pkg}.apk-new" "/etc/config/${pkg}-opkg"; do
|
||||
[ -s "$f" ] && pristine="$f" && break
|
||||
done
|
||||
|
||||
enable_add_url() {
|
||||
local url="$1" action="$2" _found
|
||||
config_load "$packageName"
|
||||
config_foreach _enable_url 'file_url' "$url" "$action"
|
||||
if [ -z "$_found" ]; then
|
||||
uci_add "$packageName" 'file_url'
|
||||
uci_set "$packageName" '@file_url[-1]' 'url' "$url"
|
||||
uci_set "$packageName" '@file_url[-1]' 'size' "$(get_url_filesize "$url")"
|
||||
uci_set "$packageName" '@file_url[-1]' 'action' "$action"
|
||||
fi
|
||||
}
|
||||
_find_name() { grep -B1 "$1" "$pristine" 2>/dev/null | head -1 | cut -d "'" -f2; }
|
||||
|
||||
if [ -s '/etc/config/simple-adblock' ] \
|
||||
&& [ ! -s '/etc/config/adblock-fast-opkg' ] \
|
||||
&& [ "$(uci_get adblock-fast config enabled)" = '0' ]; then
|
||||
cp -f '/etc/config/adblock-fast' '/etc/config/adblock-fast-opkg'
|
||||
enabled="$(uci_get simple-adblock config enabled)"
|
||||
if [ -x '/etc/init.d/simple-adblock' ]; then
|
||||
output "Stopping and disabling simple-adblock "
|
||||
if /etc/init.d/simple-adblock stop >/dev/null 2>&1 \
|
||||
&& /etc/init.d/simple-adblock disable \
|
||||
&& uci_set simple-adblock config enabled 0 \
|
||||
&& uci_commit simple-adblock; then
|
||||
output_okn
|
||||
else
|
||||
output_failn
|
||||
fi
|
||||
else
|
||||
output "Disabling simple-adblock."
|
||||
if uci_set simple-adblock config enabled 0 \
|
||||
&& uci_commit simple-adblock; then
|
||||
output_okn
|
||||
else
|
||||
output_failn
|
||||
fi
|
||||
fi
|
||||
output "Migrating simple-adblock config file "
|
||||
for i in allow_non_ascii canary_domains_icloud canary_domains_mozilla \
|
||||
compressed_cache compressed_cache_dir config_update_enabled \
|
||||
curl_additional_param curl_max_file_size curl_retry download_timeout \
|
||||
debug dns dns_instance dnsmasq_config_file_url force_dns led \
|
||||
parallel_downloads procd_trigger_wan6 procd_boot_wan_timeout verbosity; do
|
||||
j="$(uci_get simple-adblock.config.${i})"
|
||||
[ -n "$j" ] && uci_set "$packageName" config "$i" "$j"
|
||||
done
|
||||
[ -n "$enabled" ] && uci_set "$packageName" config enabled "$enabled"
|
||||
j="$(uci_get simple-adblock config config_update_url)"
|
||||
if [ "${j//simple-adblock/}" = "$j" ]; then
|
||||
uci_set "$packageName" config config_update_url "$j"
|
||||
fi
|
||||
ccd="$(uci_get simple-adblock config compressed_cache_dir '/etc')"
|
||||
for j in $(uci_get simple-adblock config allowed_domain); do
|
||||
[ -n "$j" ] && uci_add_list "$packageName" config allowed_domain "$j"
|
||||
done
|
||||
for j in $(uci_get simple-adblock config blocked_domain); do
|
||||
[ -n "$j" ] && uci_add_list "$packageName" config blocked_domain "$j"
|
||||
done
|
||||
for j in $(uci_get simple-adblock config force_dns_port); do
|
||||
[ -n "$j" ] && uci_add_list "$packageName" config force_dns_port "$j"
|
||||
done
|
||||
output_okn
|
||||
|
||||
for i in allowed_domains_url blocked_adblockplus_url blocked_domains_url \
|
||||
blocked_hosts_url; do
|
||||
output "Migrating simple-adblock ${i} "
|
||||
for j in $(uci_get simple-adblock config "$i"); do
|
||||
if [ "$i" = 'allowed_domains_url' ]; then
|
||||
enable_add_url "$j" 'allow'
|
||||
else
|
||||
enable_add_url "$j" 'block'
|
||||
if [ -n "$pristine" ]; then
|
||||
# shellcheck disable=SC1091
|
||||
. /lib/functions.sh
|
||||
add_name() {
|
||||
local cfg="$1" url name label
|
||||
config_get url "$cfg" 'url'
|
||||
config_get name "$cfg" 'name'
|
||||
if [ -z "$name" ]; then
|
||||
label="${url##*//}"; label="${label%%/*}"
|
||||
name="$(_find_name "$url")"
|
||||
if [ -n "$name" ]; then
|
||||
uci set "${pkg}.${cfg}.name=${name}"
|
||||
printf " %s: %s\n" "$label" "$name" >&2
|
||||
fi
|
||||
done
|
||||
output_okn
|
||||
done
|
||||
uci_commit "$packageName"
|
||||
output "Migrating simple-adblock cache file(s) "
|
||||
for i in '/var/run/simple-adblock/dnsmasq.addnhosts.cache' \
|
||||
'/var/run/simple-adblock/dnsmasq.conf.cache' \
|
||||
'/var/run/simple-adblock/dnsmasq.ipset.cache' \
|
||||
'/var/run/simple-adblock/dnsmasq.nftset.cache' \
|
||||
'/var/run/simple-adblock/dnsmasq.servers.cache' \
|
||||
'/var/run/simple-adblock/unbound.cache'; do
|
||||
if [ -s "$i" ]; then
|
||||
current_dir="$(dirname "$i")"
|
||||
mkdir -p "${current_dir//simple-adblock/adblock-fast}"
|
||||
mv -f "$i" "${i//simple-adblock/adblock-fast}" && output_okn || output_failn
|
||||
fi
|
||||
done
|
||||
for i in 'simple-adblock.dnsmasq.addnhosts.gz' \
|
||||
'simple-adblock.dnsmasq.conf.gz' \
|
||||
'simple-adblock.dnsmasq.ipset.gz' \
|
||||
'simple-adblock.dnsmasq.nftset.gz' \
|
||||
'simple-adblock.dnsmasq.servers.gz' \
|
||||
'simple-adblock.unbound.gz'; do
|
||||
i="${ccd}/${i}"
|
||||
if [ -s "$i" ]; then
|
||||
mkdir -p "${ccd//simple-adblock/adblock-fast}"
|
||||
mv -f "$i" "${i//simple-adblock/adblock-fast}" && output_okn || output_failn
|
||||
fi
|
||||
done
|
||||
output_okn
|
||||
fi
|
||||
|
||||
# Transition to list names
|
||||
_find_name() { grep -B1 "$1" "/etc/config/${packageName}-opkg" | head -1 | cut -d "'" -f2; }
|
||||
|
||||
add_name() {
|
||||
local cfg="$1"
|
||||
local url name label
|
||||
config_get url "$cfg" 'url'
|
||||
config_get name "$cfg" 'name'
|
||||
if [ -z "$name" ]; then
|
||||
label="${url##*//}"
|
||||
label="${label%%/*}";
|
||||
output "Finding name for ${label}: "
|
||||
name="$(_find_name "$url")"
|
||||
if [ -n "$name" ]; then
|
||||
uci_set "$packageName" "$cfg" 'name' "$name"
|
||||
output "$name "
|
||||
output_okn
|
||||
else
|
||||
output "Unknown "
|
||||
output_failn
|
||||
fi
|
||||
else
|
||||
output "Name for ${label} already set to ${name} "
|
||||
output_okn
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -s "/etc/config/${packageName}-opkg" ] && ! grep -q 'option name' "/etc/config/${packageName}"; then
|
||||
config_load "$packageName"
|
||||
}
|
||||
config_load "$pkg"
|
||||
config_foreach add_name 'file_url'
|
||||
fi
|
||||
|
||||
# migrate to 1.2.0
|
||||
oldval="$(uci_get "$packageName" 'config' 'debug')"
|
||||
# ── Migrate to 1.2.0 ────────────────────────────────────────────────
|
||||
|
||||
oldval="$(uci -q get "${pkg}.config.debug")"
|
||||
if [ -n "$oldval" ]; then
|
||||
uci_set "$packageName" 'config' 'debug_init_script' "$oldval"
|
||||
uci_remove "$packageName" 'config' 'debug'
|
||||
fi
|
||||
oldval="$(uci_get "$packageName" 'config' 'proc_debug')"
|
||||
if [ -n "$oldval" ]; then
|
||||
uci_set "$packageName" 'config' 'debug_performance' "$oldval"
|
||||
uci_remove "$packageName" 'config' 'proc_debug'
|
||||
uci set "${pkg}.config.debug_init_script=${oldval}"
|
||||
uci -q delete "${pkg}.config.debug"
|
||||
fi
|
||||
|
||||
# migrate sanity_check to dnsmasq_sanity_check
|
||||
if [ -z "$(uci_get "$packageName" 'config' 'dnsmasq_sanity_check')" ] && [ -n "$(uci_get "$packageName" 'config' 'sanity_check')" ]; then
|
||||
oldval="$(uci_get "$packageName" 'config' 'sanity_check')"
|
||||
uci_set "$packageName" 'config' 'dnsmasq_sanity_check' "$oldval"
|
||||
uci_remove "$packageName" 'config' 'sanity_check'
|
||||
oldval="$(uci -q get "${pkg}.config.proc_debug")"
|
||||
if [ -n "$oldval" ]; then
|
||||
uci set "${pkg}.config.debug_performance=${oldval}"
|
||||
uci -q delete "${pkg}.config.proc_debug"
|
||||
fi
|
||||
|
||||
uci_changes "$packageName" && uci_commit "$packageName"
|
||||
# ── Migrate sanity_check → dnsmasq_sanity_check ─────────────────────
|
||||
|
||||
if [ -z "$(uci -q get "${pkg}.config.dnsmasq_sanity_check")" ] \
|
||||
&& [ -n "$(uci -q get "${pkg}.config.sanity_check")" ]; then
|
||||
oldval="$(uci -q get "${pkg}.config.sanity_check")"
|
||||
uci set "${pkg}.config.dnsmasq_sanity_check=${oldval}"
|
||||
uci -q delete "${pkg}.config.sanity_check"
|
||||
fi
|
||||
|
||||
# ── Commit if anything changed ───────────────────────────────────────
|
||||
|
||||
[ -n "$(uci -q changes "$pkg" 2>/dev/null)" ] && uci commit "$pkg"
|
||||
|
||||
exit 0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,95 @@
|
||||
'use strict';
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Copyright 2023-2026 MOSSDeF, Stan Grishin (stangri@melmac.ca).
|
||||
//
|
||||
// CLI dispatcher for adblock-fast.
|
||||
// Called from init script:
|
||||
// ucode -S -L /lib/adblock-fast /lib/adblock-fast/cli.uc -- <action> [args...]
|
||||
|
||||
import adb from 'adblock-fast';
|
||||
|
||||
let action = shift(ARGV);
|
||||
if (action == '--') action = shift(ARGV);
|
||||
|
||||
switch (action) {
|
||||
case 'start':
|
||||
let start_result = adb.start(ARGV);
|
||||
if (start_result)
|
||||
print(adb.emit_procd_shell(start_result));
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
let stop_result = adb.stop();
|
||||
if (stop_result)
|
||||
print(adb.emit_procd_shell(stop_result));
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
adb.status_service(ARGV[0]);
|
||||
break;
|
||||
|
||||
case 'allow':
|
||||
adb.allow(join(' ', ARGV));
|
||||
break;
|
||||
|
||||
case 'check':
|
||||
adb.check(join(' ', ARGV));
|
||||
break;
|
||||
|
||||
case 'check_tld':
|
||||
adb.check_tld();
|
||||
break;
|
||||
|
||||
case 'check_leading_dot':
|
||||
adb.check_leading_dot();
|
||||
break;
|
||||
|
||||
case 'check_lists':
|
||||
adb.check_lists(join(' ', ARGV));
|
||||
break;
|
||||
|
||||
case 'dl':
|
||||
let dl_result = adb.dl();
|
||||
if (dl_result)
|
||||
print(adb.emit_procd_shell(dl_result));
|
||||
break;
|
||||
|
||||
case 'killcache':
|
||||
adb.killcache();
|
||||
break;
|
||||
|
||||
case 'pause':
|
||||
adb.pause(ARGV[0]);
|
||||
break;
|
||||
|
||||
case 'show_blocklist':
|
||||
adb.show_blocklist();
|
||||
break;
|
||||
|
||||
case 'sizes':
|
||||
adb.sizes();
|
||||
break;
|
||||
|
||||
case 'version':
|
||||
print(adb.pkg.version + '\n');
|
||||
break;
|
||||
|
||||
case 'get_wan_interfaces':
|
||||
let info = adb.get_network_trigger_info();
|
||||
if (info)
|
||||
print(sprintf('%J', info) + '\n');
|
||||
break;
|
||||
|
||||
case 'adb_config_update':
|
||||
adb.adb_config_update(ARGV[0]);
|
||||
break;
|
||||
|
||||
case 'load_environment':
|
||||
let env_ok = adb.env.load(ARGV[0], ARGV[1]);
|
||||
exit(env_ok ? 0 : 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
warn('Unknown action: ' + (action || '(none)') + '\n');
|
||||
exit(1);
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
Test that all 9 DNS modes produce valid output files containing
|
||||
domains from both the domains.txt and hosts.txt test data files.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { readfile, dirname, mkdir } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
let modes = [
|
||||
'dnsmasq.servers',
|
||||
'dnsmasq.conf',
|
||||
'dnsmasq.ipset',
|
||||
'dnsmasq.nftset',
|
||||
'dnsmasq.addnhosts',
|
||||
'smartdns.domainset',
|
||||
'smartdns.ipset',
|
||||
'smartdns.nftset',
|
||||
'unbound.adb_list',
|
||||
];
|
||||
|
||||
// Known-good domains that MUST appear in every output
|
||||
let must_have = [
|
||||
'ad.doubleclick.test.example.com',
|
||||
'tracker.analytics.test.example.com',
|
||||
'common-shared-1.test.example.com',
|
||||
'common-shared-10.test.example.com',
|
||||
'adhost-zero-1.test.example.org',
|
||||
'adhost-loopback-1.test.example.org',
|
||||
'parent-dedup-1.test.example.com',
|
||||
];
|
||||
|
||||
// Domains that MUST NOT appear (invalid entries or subdomain-deduped)
|
||||
let must_not_have = [
|
||||
'localhost',
|
||||
'nodot',
|
||||
'child.parent-dedup-1.test.example.com',
|
||||
'sub.child.parent-dedup-2.test.example.com',
|
||||
'deep.sub.parent-dedup-3.test.example.com',
|
||||
];
|
||||
|
||||
let results = [];
|
||||
|
||||
for (let mode in modes) {
|
||||
// Reset module state for each mode
|
||||
ti.env._config_loaded = false;
|
||||
ti.env._loaded = false;
|
||||
ti.env.dnsmasq_features = '';
|
||||
ti.env._detected = false;
|
||||
ti.env.dnsmasq_ubus = null;
|
||||
ti.status_data.errors = [];
|
||||
ti.status_data.warnings = [];
|
||||
ti.status_data.status = '';
|
||||
ti.status_data.message = '';
|
||||
ti.status_data.stats = '';
|
||||
|
||||
// Load config from mock UCI
|
||||
adb.env.load_config();
|
||||
|
||||
// Override DNS mode via set_cfg (load() reassigns cfg, so direct ref is stale)
|
||||
ti.set_cfg('dns', mode);
|
||||
ti.set_cfg('enabled', true);
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.env.dns_set_output_values(mode);
|
||||
|
||||
// Collect file_url sections
|
||||
ti.append_urls();
|
||||
|
||||
// Ensure output directory exists
|
||||
let out_file = ti.dns_output.file;
|
||||
let out_dir = dirname(out_file);
|
||||
mkdir(out_dir);
|
||||
|
||||
// Run the download and processing pipeline
|
||||
let ok = ti.download_lists();
|
||||
|
||||
if (!ok) {
|
||||
push(results, sprintf('%s: FAIL (download_lists returned false)', mode));
|
||||
if (length(ti.status_data.errors))
|
||||
push(results, sprintf(' errors: %J', ti.status_data.errors));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read output
|
||||
let content = readfile(out_file);
|
||||
if (!content || !length(content)) {
|
||||
push(results, sprintf('%s: FAIL (empty output file %s)', mode, out_file));
|
||||
continue;
|
||||
}
|
||||
|
||||
let lines = filter(split(content, '\n'), l => length(l) > 0);
|
||||
let line_count = length(lines);
|
||||
|
||||
// Extract domains using the mode's parse_filter
|
||||
let dm = ti.dns_modes[mode];
|
||||
let domains = {};
|
||||
let bad_format = 0;
|
||||
|
||||
for (let line in lines) {
|
||||
let domain;
|
||||
switch (mode) {
|
||||
case 'dnsmasq.servers':
|
||||
let m1 = match(line, /^server=\/([^\/]+)\/$/);
|
||||
domain = m1 ? m1[1] : null;
|
||||
break;
|
||||
case 'dnsmasq.conf':
|
||||
let m2 = match(line, /^local=\/([^\/]+)\/$/);
|
||||
domain = m2 ? m2[1] : null;
|
||||
break;
|
||||
case 'dnsmasq.ipset':
|
||||
let m3 = match(line, /^ipset=\/([^\/]+)\/adb$/);
|
||||
domain = m3 ? m3[1] : null;
|
||||
break;
|
||||
case 'dnsmasq.nftset':
|
||||
let m4 = match(line, /^nftset=\/([^\/]+)\/4#/);
|
||||
domain = m4 ? m4[1] : null;
|
||||
break;
|
||||
case 'dnsmasq.addnhosts':
|
||||
let m5 = match(line, /^127\.0\.0\.1 (.+)$/);
|
||||
domain = m5 ? m5[1] : null;
|
||||
break;
|
||||
case 'smartdns.domainset':
|
||||
case 'smartdns.ipset':
|
||||
case 'smartdns.nftset':
|
||||
domain = match(line, /^[a-zA-Z0-9._-]+$/) ? line : null;
|
||||
break;
|
||||
case 'unbound.adb_list':
|
||||
let m6 = match(line, /^local-zone: "([^"]+)\." always_nxdomain$/);
|
||||
domain = m6 ? m6[1] : null;
|
||||
if (!domain && line == 'server:') domain = '__header__';
|
||||
break;
|
||||
}
|
||||
if (domain && domain != '__header__')
|
||||
domains[domain] = true;
|
||||
else if (!domain)
|
||||
bad_format++;
|
||||
}
|
||||
|
||||
let domain_count = length(keys(domains));
|
||||
|
||||
// Check must_have domains
|
||||
let missing = [];
|
||||
for (let d in must_have) {
|
||||
if (!domains[d])
|
||||
push(missing, d);
|
||||
}
|
||||
|
||||
// Check must_not_have domains
|
||||
let unwanted = [];
|
||||
for (let d in must_not_have) {
|
||||
if (domains[d])
|
||||
push(unwanted, d);
|
||||
}
|
||||
|
||||
// dnsmasq.addnhosts doesn't do subdomain dedup (not in needs_optimization list)
|
||||
let skip_dedup = (mode == 'dnsmasq.addnhosts');
|
||||
if (skip_dedup) {
|
||||
unwanted = filter(unwanted, d =>
|
||||
d != 'child.parent-dedup-1.test.example.com' &&
|
||||
d != 'sub.child.parent-dedup-2.test.example.com' &&
|
||||
d != 'deep.sub.parent-dedup-3.test.example.com');
|
||||
}
|
||||
|
||||
if (length(missing) == 0 && length(unwanted) == 0 && bad_format == 0 && domain_count > 100)
|
||||
push(results, sprintf('%s: PASS (%d domains)', mode, domain_count));
|
||||
else {
|
||||
let detail = sprintf('%s: FAIL (%d domains, %d bad_format', mode, domain_count, bad_format);
|
||||
if (length(missing))
|
||||
detail += sprintf(', missing: %J', missing);
|
||||
if (length(unwanted))
|
||||
detail += sprintf(', unwanted: %J', unwanted);
|
||||
detail += ')';
|
||||
push(results, detail);
|
||||
}
|
||||
}
|
||||
|
||||
print(join('\n', results) + '\n');
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
dnsmasq.servers: PASS (162 domains)
|
||||
dnsmasq.conf: PASS (162 domains)
|
||||
dnsmasq.ipset: PASS (162 domains)
|
||||
dnsmasq.nftset: PASS (162 domains)
|
||||
dnsmasq.addnhosts: PASS (165 domains)
|
||||
smartdns.domainset: PASS (162 domains)
|
||||
smartdns.ipset: PASS (162 domains)
|
||||
smartdns.nftset: PASS (162 domains)
|
||||
unbound.adb_list: PASS (162 domains)
|
||||
-- End --
|
||||
@@ -0,0 +1,42 @@
|
||||
Test that detect_file_type() correctly identifies all supported list formats.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { writefile, mkdir } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
adb.env.load_config();
|
||||
|
||||
let test_dir = '' + TESTDIR + '/fmt_test';
|
||||
mkdir(test_dir);
|
||||
|
||||
let tests = [
|
||||
['domains', 'example.com\nad.tracker.net\nmalware.bad.org\n'],
|
||||
['hosts', '0.0.0.0 example.com\n127.0.0.1 tracker.net\n0.0.0.0 malware.org\n'],
|
||||
['adblockplus', '[Adblock Plus]\n||example.com^\n||tracker.net^\n'],
|
||||
['dnsmasq', 'server=/example.com/\nserver=/tracker.net/\n'],
|
||||
];
|
||||
|
||||
let results = [];
|
||||
|
||||
for (let t in tests) {
|
||||
let name = t[0];
|
||||
let content = t[1];
|
||||
let path = test_dir + '/' + name + '.txt';
|
||||
writefile(path, content);
|
||||
let detected = ti.detect_file_type(path);
|
||||
if (detected == name)
|
||||
push(results, sprintf('%s: PASS', name));
|
||||
else
|
||||
push(results, sprintf('%s: FAIL (detected as %s)', name, detected));
|
||||
}
|
||||
|
||||
print(join('\n', results) + '\n');
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
domains: PASS
|
||||
hosts: PASS
|
||||
adblockplus: PASS
|
||||
dnsmasq: PASS
|
||||
-- End --
|
||||
@@ -0,0 +1,58 @@
|
||||
Test that subdomain dedup removes child domains when parent exists.
|
||||
Parent domains are in domains.txt, children are in hosts.txt.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { readfile } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
adb.env.load_config();
|
||||
ti.set_cfg('dns', 'dnsmasq.servers');
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.env.dns_set_output_values('dnsmasq.servers');
|
||||
ti.append_urls();
|
||||
|
||||
let ok = ti.download_lists();
|
||||
if (!ok) {
|
||||
print('download_lists failed\n');
|
||||
} else {
|
||||
let content = readfile(ti.dns_output.file) || '';
|
||||
|
||||
// Parents must exist
|
||||
let parents = [
|
||||
'parent-dedup-1.test.example.com',
|
||||
'parent-dedup-2.test.example.com',
|
||||
'parent-dedup-3.test.example.com',
|
||||
];
|
||||
// Children must be removed
|
||||
let children = [
|
||||
'child.parent-dedup-1.test.example.com',
|
||||
'sub.child.parent-dedup-2.test.example.com',
|
||||
'deep.sub.parent-dedup-3.test.example.com',
|
||||
];
|
||||
|
||||
let results = [];
|
||||
for (let p in parents) {
|
||||
let found = index(content, 'server=/' + p + '/') >= 0;
|
||||
push(results, sprintf('parent %s: %s', p, found ? 'PRESENT' : 'MISSING'));
|
||||
}
|
||||
for (let c in children) {
|
||||
let found = index(content, 'server=/' + c + '/') >= 0;
|
||||
push(results, sprintf('child %s: %s', c, found ? 'PRESENT (BAD)' : 'REMOVED'));
|
||||
}
|
||||
print(join('\n', results) + '\n');
|
||||
}
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
parent parent-dedup-1.test.example.com: PRESENT
|
||||
parent parent-dedup-2.test.example.com: PRESENT
|
||||
parent parent-dedup-3.test.example.com: PRESENT
|
||||
child child.parent-dedup-1.test.example.com: REMOVED
|
||||
child sub.child.parent-dedup-2.test.example.com: REMOVED
|
||||
child deep.sub.parent-dedup-3.test.example.com: REMOVED
|
||||
-- End --
|
||||
@@ -0,0 +1,55 @@
|
||||
Test that allowed_domain config option removes domains and subdomains from output.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { readfile } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
adb.env.load_config();
|
||||
ti.set_cfg('dns', 'dnsmasq.servers');
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.set_cfg('allowed_domain', 'ad.doubleclick.test.example.com tracker.analytics.test.example.com');
|
||||
ti.env.dns_set_output_values('dnsmasq.servers');
|
||||
ti.append_urls();
|
||||
|
||||
let ok = ti.download_lists();
|
||||
if (!ok) {
|
||||
print('download_lists failed\n');
|
||||
} else {
|
||||
let content = readfile(ti.dns_output.file) || '';
|
||||
|
||||
// These should be removed by allowed_domain
|
||||
let allowed = [
|
||||
'ad.doubleclick.test.example.com',
|
||||
'tracker.analytics.test.example.com',
|
||||
];
|
||||
// This should still be present
|
||||
let blocked = [
|
||||
'pixel.tracking.test.example.com',
|
||||
'beacon.metrics.test.example.com',
|
||||
];
|
||||
|
||||
let results = [];
|
||||
for (let a in allowed) {
|
||||
// Check for block format "server=/domain/\n" — not allow format "server=/domain/#\n"
|
||||
let found = index(content, 'server=/' + a + '/\n') >= 0;
|
||||
push(results, sprintf('allowed %s: %s', a, found ? 'STILL PRESENT (BAD)' : 'REMOVED'));
|
||||
}
|
||||
for (let b in blocked) {
|
||||
let found = index(content, 'server=/' + b + '/\n') >= 0;
|
||||
push(results, sprintf('blocked %s: %s', b, found ? 'PRESENT' : 'MISSING'));
|
||||
}
|
||||
print(join('\n', results) + '\n');
|
||||
}
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
allowed ad.doubleclick.test.example.com: REMOVED
|
||||
allowed tracker.analytics.test.example.com: REMOVED
|
||||
blocked pixel.tracking.test.example.com: PRESENT
|
||||
blocked beacon.metrics.test.example.com: PRESENT
|
||||
-- End --
|
||||
@@ -0,0 +1,45 @@
|
||||
Test that canary domains are injected when enabled.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { readfile } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
adb.env.load_config();
|
||||
ti.set_cfg('dns', 'dnsmasq.servers');
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.set_cfg('canary_domains_icloud', true);
|
||||
ti.set_cfg('canary_domains_mozilla', true);
|
||||
ti.env.dns_set_output_values('dnsmasq.servers');
|
||||
ti.append_urls();
|
||||
|
||||
let ok = ti.download_lists();
|
||||
if (!ok) {
|
||||
print('download_lists failed\n');
|
||||
} else {
|
||||
let content = readfile(ti.dns_output.file) || '';
|
||||
|
||||
let canary_domains = [
|
||||
'mask.icloud.com',
|
||||
'mask-h2.icloud.com',
|
||||
'use-application-dns.net',
|
||||
];
|
||||
|
||||
let results = [];
|
||||
for (let d in canary_domains) {
|
||||
let found = index(content, 'server=/' + d + '/') >= 0;
|
||||
push(results, sprintf('canary %s: %s', d, found ? 'PRESENT' : 'MISSING'));
|
||||
}
|
||||
print(join('\n', results) + '\n');
|
||||
}
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
canary mask.icloud.com: PRESENT
|
||||
canary mask-h2.icloud.com: PRESENT
|
||||
canary use-application-dns.net: PRESENT
|
||||
-- End --
|
||||
@@ -0,0 +1,57 @@
|
||||
Test that dnsmasq.servers mode prepends explicit allow entries (server=/domain/#)
|
||||
when allowed_domain is set.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { readfile } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
adb.env.load_config();
|
||||
ti.set_cfg('dns', 'dnsmasq.servers');
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.set_cfg('allowed_domain', 'safe.example.com also-safe.example.com');
|
||||
ti.env.dns_set_output_values('dnsmasq.servers');
|
||||
ti.append_urls();
|
||||
|
||||
let ok = ti.download_lists();
|
||||
if (!ok) {
|
||||
print('download_lists failed\n');
|
||||
} else {
|
||||
let content = readfile(ti.dns_output.file) || '';
|
||||
let lines = filter(split(content, '\n'), l => length(l) > 0);
|
||||
|
||||
// In servers mode with allowed_domain, server=/domain/# entries are prepended
|
||||
let allow_entries = filter(lines, l => match(l, /\/#$/));
|
||||
let block_entries = filter(lines, l => match(l, /\/$/));
|
||||
|
||||
let results = [];
|
||||
push(results, sprintf('allow_entries: %d', length(allow_entries)));
|
||||
push(results, sprintf('block_entries: %d', length(block_entries)));
|
||||
|
||||
// Check specific allow entries
|
||||
let has_safe = index(content, 'server=/safe.example.com/#') >= 0;
|
||||
let has_also = index(content, 'server=/also-safe.example.com/#') >= 0;
|
||||
push(results, sprintf('safe.example.com allow: %s', has_safe ? 'PRESENT' : 'MISSING'));
|
||||
push(results, sprintf('also-safe.example.com allow: %s', has_also ? 'PRESENT' : 'MISSING'));
|
||||
|
||||
// Allow entries should be at the top
|
||||
if (length(allow_entries) > 0 && length(lines) > 0) {
|
||||
let first_is_allow = match(lines[0], /\/#$/);
|
||||
push(results, sprintf('allow_entries_at_top: %s', first_is_allow ? 'YES' : 'NO'));
|
||||
}
|
||||
|
||||
print(join('\n', results) + '\n');
|
||||
}
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
allow_entries: 2
|
||||
block_entries: 162
|
||||
safe.example.com allow: PRESENT
|
||||
also-safe.example.com allow: PRESENT
|
||||
allow_entries_at_top: YES
|
||||
-- End --
|
||||
@@ -0,0 +1,50 @@
|
||||
Test that dnsmasq.addnhosts with ipv6_enabled produces both
|
||||
127.0.0.1 and :: entries (dual-stack).
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { readfile } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
adb.env.load_config();
|
||||
ti.set_cfg('dns', 'dnsmasq.addnhosts');
|
||||
ti.set_cfg('ipv6_enabled', true);
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.env.dns_set_output_values('dnsmasq.addnhosts');
|
||||
ti.append_urls();
|
||||
|
||||
let ok = ti.download_lists();
|
||||
if (!ok) {
|
||||
print('download_lists failed\n');
|
||||
} else {
|
||||
let content = readfile(ti.dns_output.file) || '';
|
||||
let lines = filter(split(content, '\n'), l => length(l) > 0);
|
||||
|
||||
let ipv4_lines = filter(lines, l => match(l, /^127\.0\.0\.1 /));
|
||||
let ipv6_lines = filter(lines, l => match(l, /^:: /));
|
||||
|
||||
let results = [];
|
||||
push(results, sprintf('ipv4_count: %d', length(ipv4_lines)));
|
||||
push(results, sprintf('ipv6_count: %d', length(ipv6_lines)));
|
||||
push(results, sprintf('counts_match: %s', length(ipv4_lines) == length(ipv6_lines) ? 'YES' : 'NO'));
|
||||
|
||||
// Spot-check a specific domain appears in both
|
||||
let test_domain = 'ad.doubleclick.test.example.com';
|
||||
let has_ipv4 = index(content, '127.0.0.1 ' + test_domain) >= 0;
|
||||
let has_ipv6 = index(content, ':: ' + test_domain) >= 0;
|
||||
push(results, sprintf('dual_stack_%s: %s', test_domain, (has_ipv4 && has_ipv6) ? 'YES' : 'NO'));
|
||||
|
||||
print(join('\n', results) + '\n');
|
||||
}
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
ipv4_count: 165
|
||||
ipv6_count: 165
|
||||
counts_match: YES
|
||||
dual_stack_ad.doubleclick.test.example.com: YES
|
||||
-- End --
|
||||
@@ -0,0 +1,54 @@
|
||||
Test that dnsmasq.nftset with ipv6_enabled includes IPv6 set reference.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { readfile } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
adb.env.load_config();
|
||||
ti.set_cfg('dns', 'dnsmasq.nftset');
|
||||
ti.set_cfg('ipv6_enabled', true);
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.env.dns_set_output_values('dnsmasq.nftset');
|
||||
ti.append_urls();
|
||||
|
||||
let ok = ti.download_lists();
|
||||
if (!ok) {
|
||||
print('download_lists failed\n');
|
||||
} else {
|
||||
let content = readfile(ti.dns_output.file) || '';
|
||||
let lines = filter(split(content, '\n'), l => length(l) > 0);
|
||||
|
||||
// With IPv6 enabled, nftset format should include ,6#inet#fw4#adb6
|
||||
let ipv6_lines = filter(lines, l => match(l, /6#inet#fw4#adb6/));
|
||||
let ipv4_only = filter(lines, l => match(l, /4#inet#fw4#adb4/) && !match(l, /6#inet#fw4#adb6/));
|
||||
|
||||
let results = [];
|
||||
push(results, sprintf('total_lines: %d', length(lines)));
|
||||
push(results, sprintf('with_ipv6_set: %d', length(ipv6_lines)));
|
||||
push(results, sprintf('ipv4_only: %d', length(ipv4_only)));
|
||||
|
||||
// All lines should have both IPv4 and IPv6 set references
|
||||
push(results, sprintf('all_dual_stack: %s', (length(ipv6_lines) == length(lines)) ? 'YES' : 'NO'));
|
||||
|
||||
// Spot-check format
|
||||
let test_domain = 'ad.doubleclick.test.example.com';
|
||||
let expected = 'nftset=/' + test_domain + '/4#inet#fw4#adb4,6#inet#fw4#adb6';
|
||||
let has_entry = index(content, expected) >= 0;
|
||||
push(results, sprintf('correct_format: %s', has_entry ? 'YES' : 'NO'));
|
||||
|
||||
print(join('\n', results) + '\n');
|
||||
}
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
total_lines: 162
|
||||
with_ipv6_set: 162
|
||||
ipv4_only: 0
|
||||
all_dual_stack: YES
|
||||
correct_format: YES
|
||||
-- End --
|
||||
@@ -0,0 +1,48 @@
|
||||
Test that unbound.adb_list mode prepends "server:" header line.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { readfile } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
adb.env.load_config();
|
||||
ti.set_cfg('dns', 'unbound.adb_list');
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.env.dns_set_output_values('unbound.adb_list');
|
||||
ti.append_urls();
|
||||
|
||||
let ok = ti.download_lists();
|
||||
if (!ok) {
|
||||
print('download_lists failed\n');
|
||||
} else {
|
||||
let content = readfile(ti.dns_output.file) || '';
|
||||
let lines = filter(split(content, '\n'), l => length(l) > 0);
|
||||
|
||||
let results = [];
|
||||
|
||||
// First line should be "server:"
|
||||
push(results, sprintf('first_line: %s', lines[0]));
|
||||
|
||||
// Rest should be local-zone entries
|
||||
let lz_lines = filter(lines, l => match(l, /^local-zone: /));
|
||||
push(results, sprintf('local_zone_entries: %d', length(lz_lines)));
|
||||
|
||||
// Spot-check format
|
||||
let test_domain = 'ad.doubleclick.test.example.com';
|
||||
let expected = 'local-zone: "' + test_domain + '." always_nxdomain';
|
||||
let has_entry = index(content, expected) >= 0;
|
||||
push(results, sprintf('correct_format: %s', has_entry ? 'YES' : 'NO'));
|
||||
|
||||
print(join('\n', results) + '\n');
|
||||
}
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
first_line: server:
|
||||
local_zone_entries: 162
|
||||
correct_format: YES
|
||||
-- End --
|
||||
@@ -0,0 +1,41 @@
|
||||
Test that cfg.blocked_domain entries are added to the output.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { readfile } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
adb.env.load_config();
|
||||
ti.set_cfg('dns', 'dnsmasq.servers');
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.set_cfg('blocked_domain', 'custom-block-1.example.net custom-block-2.example.net');
|
||||
ti.env.dns_set_output_values('dnsmasq.servers');
|
||||
ti.append_urls();
|
||||
|
||||
let ok = ti.download_lists();
|
||||
if (!ok) {
|
||||
print('download_lists failed\n');
|
||||
} else {
|
||||
let content = readfile(ti.dns_output.file) || '';
|
||||
|
||||
let customs = [
|
||||
'custom-block-1.example.net',
|
||||
'custom-block-2.example.net',
|
||||
];
|
||||
let results = [];
|
||||
for (let d in customs) {
|
||||
let found = index(content, 'server=/' + d + '/') >= 0;
|
||||
push(results, sprintf('%s: %s', d, found ? 'PRESENT' : 'MISSING'));
|
||||
}
|
||||
print(join('\n', results) + '\n');
|
||||
}
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
custom-block-1.example.net: PRESENT
|
||||
custom-block-2.example.net: PRESENT
|
||||
-- End --
|
||||
@@ -0,0 +1,33 @@
|
||||
Test that check() correctly identifies blocked and unblocked domains.
|
||||
check() output goes to stderr via output.info/output.print when is_tty=true.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
// Build the blocklist
|
||||
adb.env.load_config();
|
||||
ti.set_cfg('dns', 'dnsmasq.servers');
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.env.dns_set_output_values('dnsmasq.servers');
|
||||
ti.append_urls();
|
||||
ti.download_lists();
|
||||
|
||||
// Enable tty mode so output goes to stderr (captured by test framework)
|
||||
// Use verbosity=2 to get verbose output (which includes [PROC] prefix)
|
||||
ti.state.is_tty = true;
|
||||
ti.set_cfg('verbosity', 2);
|
||||
|
||||
// Now test check()
|
||||
adb.check('ad.doubleclick.test.example.com this-domain-not-in-list.test.example.com');
|
||||
-- End --
|
||||
|
||||
-- Expect stderr --
|
||||
[PROC] Found 1 match for 'ad.doubleclick.test.example.com' in 'TESTDIR/var_run/adblock-fast/dnsmasq.servers'.
|
||||
ad.doubleclick.test.example.com
|
||||
[PROC] The 'this-domain-not-in-list.test.example.com' is not found in current block-list ('TESTDIR/var_run/adblock-fast/dnsmasq.servers').
|
||||
-- End --
|
||||
@@ -0,0 +1,53 @@
|
||||
Test that show_blocklist() outputs parsed domains to stdout.
|
||||
|
||||
-- Testcase --
|
||||
import adb from 'adblock-fast';
|
||||
import { popen } from 'fs';
|
||||
let ti = adb._test_internals;
|
||||
|
||||
// Build the blocklist
|
||||
adb.env.load_config();
|
||||
ti.set_cfg('dns', 'dnsmasq.servers');
|
||||
ti.set_cfg('dnsmasq_sanity_check', false);
|
||||
ti.set_cfg('dnsmasq_validity_check', false);
|
||||
ti.set_cfg('heartbeat_domain', null);
|
||||
ti.set_cfg('config_update_enabled', false);
|
||||
ti.set_cfg('update_config_sizes', false);
|
||||
ti.env.dns_set_output_values('dnsmasq.servers');
|
||||
ti.append_urls();
|
||||
ti.download_lists();
|
||||
|
||||
// show_blocklist() uses system() to run sed parse_filter on the output file,
|
||||
// which outputs plain domains to stdout. Count lines and check a few domains.
|
||||
let cmd = sprintf("sed '%s' %s", ti.dns_output.parse_filter, ti.dns_output.file);
|
||||
let p = popen(cmd, 'r');
|
||||
let out = p ? (p.read('all') || '') : '';
|
||||
if (p) p.close();
|
||||
|
||||
let domains = filter(split(out, '\n'), l => length(l) > 0);
|
||||
let count = length(domains);
|
||||
|
||||
let must_have = [
|
||||
'ad.doubleclick.test.example.com',
|
||||
'common-shared-1.test.example.com',
|
||||
'adhost-zero-1.test.example.org',
|
||||
];
|
||||
|
||||
let domain_set = {};
|
||||
for (let d in domains) domain_set[d] = true;
|
||||
|
||||
let results = [];
|
||||
push(results, sprintf('domain_count: %d', count));
|
||||
for (let d in must_have) {
|
||||
push(results, sprintf('%s: %s', d, domain_set[d] ? 'PRESENT' : 'MISSING'));
|
||||
}
|
||||
|
||||
print(join('\n', results) + '\n');
|
||||
-- End --
|
||||
|
||||
-- Expect stdout --
|
||||
domain_count: 162
|
||||
ad.doubleclick.test.example.com: PRESENT
|
||||
common-shared-1.test.example.com: PRESENT
|
||||
adhost-zero-1.test.example.org: PRESENT
|
||||
-- End --
|
||||
@@ -0,0 +1,26 @@
|
||||
[Adblock Plus 2.0]
|
||||
! Title: Test ABP blocklist
|
||||
! Last modified: 2024-01-01
|
||||
! Homepage: https://test.example.com
|
||||
|
||||
||abp-tracker-1.test.example.com^
|
||||
||abp-tracker-2.test.example.com^
|
||||
||abp-tracker-3.test.example.com^
|
||||
||abp-tracker-4.test.example.com^
|
||||
||abp-tracker-5.test.example.com^
|
||||
||abp-tracker-6.test.example.com^
|
||||
||abp-tracker-7.test.example.com^
|
||||
||abp-tracker-8.test.example.com^
|
||||
||abp-tracker-9.test.example.com^
|
||||
||abp-tracker-10.test.example.com^
|
||||
||abp-tracker-11.test.example.com^$third-party
|
||||
||abp-tracker-12.test.example.com^$third-party
|
||||
||abp-tracker-13.test.example.com^
|
||||
||abp-tracker-14.test.example.com^
|
||||
||abp-tracker-15.test.example.com^
|
||||
! Comment between entries
|
||||
||abp-tracker-16.test.example.com^
|
||||
||abp-tracker-17.test.example.com^
|
||||
||abp-tracker-18.test.example.com^
|
||||
||abp-tracker-19.test.example.com^
|
||||
||abp-tracker-20.test.example.com^
|
||||
@@ -0,0 +1,6 @@
|
||||
# Allowed domains list (hosts format, used with action=allow)
|
||||
0.0.0.0 adhost-zero-1.test.example.org
|
||||
0.0.0.0 adhost-zero-2.test.example.org
|
||||
0.0.0.0 adhost-zero-3.test.example.org
|
||||
0.0.0.0 common-shared-1.test.example.com
|
||||
0.0.0.0 common-shared-2.test.example.com
|
||||
@@ -0,0 +1,10 @@
|
||||
server=/dnsmasq-input-1.test.example.com/
|
||||
server=/dnsmasq-input-2.test.example.com/
|
||||
server=/dnsmasq-input-3.test.example.com/
|
||||
server=/dnsmasq-input-4.test.example.com/
|
||||
server=/dnsmasq-input-5.test.example.com/
|
||||
server=/dnsmasq-input-6.test.example.com/
|
||||
server=/dnsmasq-input-7.test.example.com/
|
||||
server=/dnsmasq-input-8.test.example.com/
|
||||
server=/dnsmasq-input-9.test.example.com/
|
||||
server=/dnsmasq-input-10.test.example.com/
|
||||
@@ -0,0 +1,120 @@
|
||||
# Test domain blocklist for adblock-fast functional tests
|
||||
# Lines starting with # are comments and should be filtered out
|
||||
|
||||
# -- Valid domains (85 unique) --
|
||||
ad.doubleclick.test.example.com
|
||||
ads.bigadnetwork.test.example.com
|
||||
tracker.analytics.test.example.com
|
||||
pixel.tracking.test.example.com
|
||||
beacon.metrics.test.example.com
|
||||
telemetry.data.test.example.com
|
||||
stats.counter.test.example.com
|
||||
banner.display.test.example.com
|
||||
popup.overlay.test.example.com
|
||||
interstitial.fullpage.test.example.com
|
||||
video.preroll.test.example.com
|
||||
native.sponsored.test.example.com
|
||||
widget.recommendation.test.example.com
|
||||
feed.promoted.test.example.com
|
||||
sidebar.adunit.test.example.com
|
||||
footer.adzone.test.example.com
|
||||
header.leaderboard.test.example.com
|
||||
skyscraper.tower.test.example.com
|
||||
rectangle.medium.test.example.com
|
||||
billboard.jumbo.test.example.com
|
||||
expandable.rich.test.example.com
|
||||
floating.sticky.test.example.com
|
||||
adhesion.anchor.test.example.com
|
||||
pushdown.slide.test.example.com
|
||||
catfish.bottom.test.example.com
|
||||
curtain.takeover.test.example.com
|
||||
skin.wrap.test.example.com
|
||||
roadblock.wall.test.example.com
|
||||
splash.welcome.test.example.com
|
||||
exit.intent.test.example.com
|
||||
retarget.remarket.test.example.com
|
||||
lookalike.audience.test.example.com
|
||||
segment.profile.test.example.com
|
||||
cookie.sync.test.example.com
|
||||
fingerprint.device.test.example.com
|
||||
supercookie.persist.test.example.com
|
||||
evercookie.track.test.example.com
|
||||
canvas.fp.test.example.com
|
||||
webgl.hash.test.example.com
|
||||
audio.context.test.example.com
|
||||
font.enum.test.example.com
|
||||
plugin.detect.test.example.com
|
||||
screen.res.test.example.com
|
||||
timezone.offset.test.example.com
|
||||
language.pref.test.example.com
|
||||
dnt.ignore.test.example.com
|
||||
referer.leak.test.example.com
|
||||
click.redirect.test.example.com
|
||||
impression.log.test.example.com
|
||||
conversion.pixel.test.example.com
|
||||
attribution.track.test.example.com
|
||||
viewability.measure.test.example.com
|
||||
brand.safety.test.example.com
|
||||
fraud.detect.test.example.com
|
||||
bot.filter.test.example.com
|
||||
programmatic.bid.test.example.com
|
||||
realtime.auction.test.example.com
|
||||
demand.side.test.example.com
|
||||
supply.platform.test.example.com
|
||||
exchange.market.test.example.com
|
||||
mediation.waterfall.test.example.com
|
||||
prebid.header.test.example.com
|
||||
openrtb.proto.test.example.com
|
||||
vast.vpaid.test.example.com
|
||||
mraid.sdk.test.example.com
|
||||
gdpr.consent.test.example.com
|
||||
ccpa.optout.test.example.com
|
||||
tcf.vendor.test.example.com
|
||||
cmp.dialog.test.example.com
|
||||
ab.testing.test.example.com
|
||||
multivariate.experiment.test.example.com
|
||||
heatmap.session.test.example.com
|
||||
scroll.depth.test.example.com
|
||||
funnel.analysis.test.example.com
|
||||
cohort.study.test.example.com
|
||||
survey.feedback.test.example.com
|
||||
nps.score.test.example.com
|
||||
crm.integration.test.example.com
|
||||
cdp.unify.test.example.com
|
||||
dmp.segment.test.example.com
|
||||
tag.manager.test.example.com
|
||||
container.script.test.example.com
|
||||
|
||||
# -- Domains shared with hosts.txt (10 overlapping) --
|
||||
common-shared-1.test.example.com
|
||||
common-shared-2.test.example.com
|
||||
common-shared-3.test.example.com
|
||||
common-shared-4.test.example.com
|
||||
common-shared-5.test.example.com
|
||||
common-shared-6.test.example.com
|
||||
common-shared-7.test.example.com
|
||||
common-shared-8.test.example.com
|
||||
common-shared-9.test.example.com
|
||||
common-shared-10.test.example.com
|
||||
|
||||
# -- Subdomain dedup test pairs (parent in this file) --
|
||||
parent-dedup-1.test.example.com
|
||||
parent-dedup-2.test.example.com
|
||||
parent-dedup-3.test.example.com
|
||||
|
||||
# -- Invalid entries (should be filtered out) --
|
||||
# Comment only (filtered by /^#/d)
|
||||
192.168.1.1
|
||||
10.0.0.1
|
||||
127.0.0.1
|
||||
0.0.0.0
|
||||
255.255.255.255
|
||||
-starts-with-dash.test.example.com
|
||||
.starts-with-dot.test.example.com
|
||||
ends-with-dot.test.example.com.
|
||||
has..double.dots.test.example.com
|
||||
has spaces in it.test.example.com
|
||||
has@symbol.test.example.com
|
||||
has!bang.test.example.com
|
||||
nodot
|
||||
just a random sentence
|
||||
@@ -0,0 +1,108 @@
|
||||
# Test hosts blocklist for adblock-fast functional tests
|
||||
# Title: StevenBlack Unified hosts (adware + malware)
|
||||
#
|
||||
# This block between "# Title: StevenBlack" and "# Custom host records"
|
||||
# is specifically removed by adblock-fast (lines 1589-1590).
|
||||
# Entries here should NOT appear in the output.
|
||||
# stevenblack-should-not-appear.test.example.com
|
||||
0.0.0.0 stevenblack-entry-hidden.test.example.com
|
||||
# Custom host records are listed here
|
||||
|
||||
# -- Standard localhost entries (should be filtered) --
|
||||
127.0.0.1 localhost
|
||||
127.0.0.1 localhost.localdomain
|
||||
127.0.0.1 local
|
||||
0.0.0.0 0.0.0.0
|
||||
::1 localhost
|
||||
|
||||
# -- Valid host entries with 0.0.0.0 prefix (40 unique) --
|
||||
0.0.0.0 adhost-zero-1.test.example.org
|
||||
0.0.0.0 adhost-zero-2.test.example.org
|
||||
0.0.0.0 adhost-zero-3.test.example.org
|
||||
0.0.0.0 adhost-zero-4.test.example.org
|
||||
0.0.0.0 adhost-zero-5.test.example.org
|
||||
0.0.0.0 adhost-zero-6.test.example.org
|
||||
0.0.0.0 adhost-zero-7.test.example.org
|
||||
0.0.0.0 adhost-zero-8.test.example.org
|
||||
0.0.0.0 adhost-zero-9.test.example.org
|
||||
0.0.0.0 adhost-zero-10.test.example.org
|
||||
0.0.0.0 adhost-zero-11.test.example.org
|
||||
0.0.0.0 adhost-zero-12.test.example.org
|
||||
0.0.0.0 adhost-zero-13.test.example.org
|
||||
0.0.0.0 adhost-zero-14.test.example.org
|
||||
0.0.0.0 adhost-zero-15.test.example.org
|
||||
0.0.0.0 adhost-zero-16.test.example.org
|
||||
0.0.0.0 adhost-zero-17.test.example.org
|
||||
0.0.0.0 adhost-zero-18.test.example.org
|
||||
0.0.0.0 adhost-zero-19.test.example.org
|
||||
0.0.0.0 adhost-zero-20.test.example.org
|
||||
0.0.0.0 adhost-zero-21.test.example.org
|
||||
0.0.0.0 adhost-zero-22.test.example.org
|
||||
0.0.0.0 adhost-zero-23.test.example.org
|
||||
0.0.0.0 adhost-zero-24.test.example.org
|
||||
0.0.0.0 adhost-zero-25.test.example.org
|
||||
0.0.0.0 adhost-zero-26.test.example.org
|
||||
0.0.0.0 adhost-zero-27.test.example.org
|
||||
0.0.0.0 adhost-zero-28.test.example.org
|
||||
0.0.0.0 adhost-zero-29.test.example.org
|
||||
0.0.0.0 adhost-zero-30.test.example.org
|
||||
0.0.0.0 adhost-zero-31.test.example.org
|
||||
0.0.0.0 adhost-zero-32.test.example.org
|
||||
0.0.0.0 adhost-zero-33.test.example.org
|
||||
0.0.0.0 adhost-zero-34.test.example.org
|
||||
0.0.0.0 adhost-zero-35.test.example.org
|
||||
0.0.0.0 adhost-zero-36.test.example.org
|
||||
0.0.0.0 adhost-zero-37.test.example.org
|
||||
0.0.0.0 adhost-zero-38.test.example.org
|
||||
0.0.0.0 adhost-zero-39.test.example.org
|
||||
0.0.0.0 adhost-zero-40.test.example.org
|
||||
|
||||
# -- Valid host entries with 127.0.0.1 prefix (25 unique) --
|
||||
127.0.0.1 adhost-loopback-1.test.example.org
|
||||
127.0.0.1 adhost-loopback-2.test.example.org
|
||||
127.0.0.1 adhost-loopback-3.test.example.org
|
||||
127.0.0.1 adhost-loopback-4.test.example.org
|
||||
127.0.0.1 adhost-loopback-5.test.example.org
|
||||
127.0.0.1 adhost-loopback-6.test.example.org
|
||||
127.0.0.1 adhost-loopback-7.test.example.org
|
||||
127.0.0.1 adhost-loopback-8.test.example.org
|
||||
127.0.0.1 adhost-loopback-9.test.example.org
|
||||
127.0.0.1 adhost-loopback-10.test.example.org
|
||||
127.0.0.1 adhost-loopback-11.test.example.org
|
||||
127.0.0.1 adhost-loopback-12.test.example.org
|
||||
127.0.0.1 adhost-loopback-13.test.example.org
|
||||
127.0.0.1 adhost-loopback-14.test.example.org
|
||||
127.0.0.1 adhost-loopback-15.test.example.org
|
||||
127.0.0.1 adhost-loopback-16.test.example.org
|
||||
127.0.0.1 adhost-loopback-17.test.example.org
|
||||
127.0.0.1 adhost-loopback-18.test.example.org
|
||||
127.0.0.1 adhost-loopback-19.test.example.org
|
||||
127.0.0.1 adhost-loopback-20.test.example.org
|
||||
127.0.0.1 adhost-loopback-21.test.example.org
|
||||
127.0.0.1 adhost-loopback-22.test.example.org
|
||||
127.0.0.1 adhost-loopback-23.test.example.org
|
||||
127.0.0.1 adhost-loopback-24.test.example.org
|
||||
127.0.0.1 adhost-loopback-25.test.example.org
|
||||
|
||||
# -- Domains shared with domains.txt (10 overlapping) --
|
||||
0.0.0.0 common-shared-1.test.example.com
|
||||
0.0.0.0 common-shared-2.test.example.com
|
||||
0.0.0.0 common-shared-3.test.example.com
|
||||
0.0.0.0 common-shared-4.test.example.com
|
||||
0.0.0.0 common-shared-5.test.example.com
|
||||
127.0.0.1 common-shared-6.test.example.com
|
||||
127.0.0.1 common-shared-7.test.example.com
|
||||
127.0.0.1 common-shared-8.test.example.com
|
||||
127.0.0.1 common-shared-9.test.example.com
|
||||
127.0.0.1 common-shared-10.test.example.com
|
||||
|
||||
# -- Subdomain dedup test (children that should be removed when parent exists) --
|
||||
0.0.0.0 child.parent-dedup-1.test.example.com
|
||||
0.0.0.0 sub.child.parent-dedup-2.test.example.com
|
||||
0.0.0.0 deep.sub.parent-dedup-3.test.example.com
|
||||
|
||||
# -- Invalid entries --
|
||||
some random text without IP prefix
|
||||
0.0.0.0
|
||||
127.0.0.1
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Hybrid mocklib for adblock-fast functional tests.
|
||||
//
|
||||
// Key difference from mwan4's mocklib:
|
||||
// - Does NOT mock the 'fs' module (real filesystem operations)
|
||||
// - Selectively overrides system() to block service management
|
||||
// while passing through data-processing commands (sed/sort/grep/awk)
|
||||
// - Mocks only 'uci' and 'ubus'
|
||||
|
||||
'use strict';
|
||||
|
||||
if (!exists(global, 'REQUIRE_SEARCH_PATH'))
|
||||
global.REQUIRE_SEARCH_PATH = [];
|
||||
|
||||
if (!exists(global, 'MOCK_SEARCH_PATH'))
|
||||
global.MOCK_SEARCH_PATH = [];
|
||||
|
||||
if (!exists(global, 'TRACE_CALLS'))
|
||||
global.TRACE_CALLS = null;
|
||||
|
||||
let _fs = require("fs");
|
||||
|
||||
// Force reloading uci and ubus modules so our mocks intercept them.
|
||||
// Do NOT delete fs -- we want the REAL filesystem module.
|
||||
delete global.modules.uci;
|
||||
delete global.modules.ubus;
|
||||
|
||||
let _log = (level, fmt, ...args) => {
|
||||
let color, prefix;
|
||||
|
||||
switch (level) {
|
||||
case 'info':
|
||||
color = 34;
|
||||
prefix = '!';
|
||||
break;
|
||||
case 'warn':
|
||||
color = 33;
|
||||
prefix = 'W';
|
||||
break;
|
||||
case 'error':
|
||||
color = 31;
|
||||
prefix = 'E';
|
||||
break;
|
||||
default:
|
||||
color = 0;
|
||||
prefix = 'I';
|
||||
}
|
||||
|
||||
let f = sprintf("\u001b[%d;1m[%s] %s\u001b[0m", color, prefix, fmt);
|
||||
warn(replace(sprintf(f, ...args), "\n", "\n "), "\n");
|
||||
};
|
||||
|
||||
let format_json = (data) => {
|
||||
let rv;
|
||||
|
||||
let format_value = (value) => {
|
||||
switch (type(value)) {
|
||||
case "object":
|
||||
return sprintf("{ /* %d keys */ }", length(value));
|
||||
case "array":
|
||||
return sprintf("[ /* %d items */ ]", length(value));
|
||||
case "string":
|
||||
if (length(value) > 64)
|
||||
value = substr(value, 0, 64) + "...";
|
||||
return sprintf("%J", value);
|
||||
default:
|
||||
return sprintf("%J", value);
|
||||
}
|
||||
};
|
||||
|
||||
switch (type(data)) {
|
||||
case "object":
|
||||
rv = "{";
|
||||
let k = sort(keys(data));
|
||||
for (let i, n in k)
|
||||
rv += sprintf("%s %J: %s", i ? "," : "", n, format_value(data[n]));
|
||||
rv += " }";
|
||||
break;
|
||||
case "array":
|
||||
rv = "[";
|
||||
for (let i, v in data)
|
||||
rv += (i ? "," : "") + " " + format_value(v);
|
||||
rv += " ]";
|
||||
break;
|
||||
default:
|
||||
rv = format_value(data);
|
||||
}
|
||||
|
||||
return rv;
|
||||
};
|
||||
|
||||
let read_data_file = (path) => {
|
||||
for (let dir in MOCK_SEARCH_PATH) {
|
||||
let fd = _fs.open(dir + '/' + path, "r");
|
||||
|
||||
if (fd) {
|
||||
let data = fd.read("all");
|
||||
fd.close();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
let read_json_file = (path) => {
|
||||
let data = read_data_file(path);
|
||||
|
||||
if (data != null) {
|
||||
try {
|
||||
return json(data);
|
||||
}
|
||||
catch (e) {
|
||||
_log('error', "Unable to parse JSON data in %s: %s", path, e);
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
let trace_call = (ns, func, args) => {
|
||||
let msg = "[call] " +
|
||||
(ns ? ns + "." : "") +
|
||||
func;
|
||||
|
||||
for (let k, v in args) {
|
||||
msg += ' ' + k + ' <';
|
||||
|
||||
switch (type(v)) {
|
||||
case "array":
|
||||
case "object":
|
||||
msg += format_json(v);
|
||||
break;
|
||||
default:
|
||||
msg += v;
|
||||
}
|
||||
|
||||
msg += '>';
|
||||
}
|
||||
|
||||
switch (TRACE_CALLS) {
|
||||
case '1':
|
||||
case 'stdout':
|
||||
_fs.stdout.write(msg + "\n");
|
||||
break;
|
||||
case 'stderr':
|
||||
_fs.stderr.write(msg + "\n");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Prepend mocklib/ to REQUIRE_SEARCH_PATH so mock uci/ubus are found
|
||||
for (let pattern in REQUIRE_SEARCH_PATH) {
|
||||
if (!match(pattern, /\*\.uc$/))
|
||||
continue;
|
||||
|
||||
let path = replace(pattern, /\*/, 'mocklib'),
|
||||
s = _fs.stat(path);
|
||||
|
||||
if (!s || s.type != 'file')
|
||||
continue;
|
||||
|
||||
if (!length(global.MOCK_SEARCH_PATH))
|
||||
global.MOCK_SEARCH_PATH = [ replace(path, /mocklib\.uc$/, '../mocks') ];
|
||||
|
||||
unshift(REQUIRE_SEARCH_PATH, replace(path, /mocklib\.uc$/, 'mocklib/*.uc'));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!length(global.MOCK_SEARCH_PATH))
|
||||
global.MOCK_SEARCH_PATH = [ './mocks' ];
|
||||
|
||||
// Register global mocklib namespace
|
||||
global.mocklib = {
|
||||
require: function(module) {
|
||||
let path, res, ex;
|
||||
|
||||
if (type(REQUIRE_SEARCH_PATH) == "array" && index(REQUIRE_SEARCH_PATH[0], 'mocklib/*.uc') != -1)
|
||||
path = shift(REQUIRE_SEARCH_PATH);
|
||||
|
||||
try {
|
||||
res = require(module);
|
||||
}
|
||||
catch (e) {
|
||||
ex = e;
|
||||
}
|
||||
|
||||
if (path)
|
||||
unshift(REQUIRE_SEARCH_PATH, path);
|
||||
|
||||
if (ex)
|
||||
die(ex);
|
||||
|
||||
return res;
|
||||
},
|
||||
|
||||
I: (...args) => _log('info', ...args),
|
||||
N: (...args) => _log('notice', ...args),
|
||||
W: (...args) => _log('warn', ...args),
|
||||
E: (...args) => _log('error', ...args),
|
||||
|
||||
format_json,
|
||||
read_data_file,
|
||||
read_json_file,
|
||||
trace_call,
|
||||
};
|
||||
|
||||
// Selectively override system() -- block service management, pass through data processing
|
||||
global.system = ((_orig_system) => function(argv, timeout) {
|
||||
let cmd = '' + argv;
|
||||
|
||||
// Block commands that interact with system services or would hang
|
||||
if (match(cmd, /^\/etc\/init\.d\//) ||
|
||||
match(cmd, /\/usr\/bin\/logger\b/) ||
|
||||
match(cmd, /^logger\b/) ||
|
||||
match(cmd, /^sleep\b/) ||
|
||||
match(cmd, /^resolveip\b/) ||
|
||||
match(cmd, /^dnsmasq\s+--test/) ||
|
||||
match(cmd, /^ipset\b/) ||
|
||||
match(cmd, /^nft\b/) ||
|
||||
match(cmd, /^chmod\b/) ||
|
||||
match(cmd, /^chown\b/)) {
|
||||
trace_call(null, "system[blocked]", { command: cmd });
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Pass through real commands for data processing
|
||||
return _orig_system(argv, timeout);
|
||||
})(global.system);
|
||||
|
||||
// Override time() to return fixed value for reproducible tests
|
||||
global.time = function() {
|
||||
return 1615382640;
|
||||
};
|
||||
|
||||
// Override getenv -- return null to prevent env interference
|
||||
global.getenv = function(key) {
|
||||
return null;
|
||||
};
|
||||
|
||||
return global.mocklib;
|
||||
@@ -0,0 +1,69 @@
|
||||
// UBus mock for adblock-fast tests.
|
||||
// Reused from mwan4's mock with no changes.
|
||||
|
||||
let mocklib = global.mocklib; // ucode-lsp disable
|
||||
|
||||
return {
|
||||
connect: function() {
|
||||
let self = this;
|
||||
|
||||
return {
|
||||
call: (object, method, args) => {
|
||||
let signature = [ object + "~" + method ];
|
||||
|
||||
if (type(args) == "object") {
|
||||
for (let i, k in sort(keys(args))) {
|
||||
switch (type(args[k])) {
|
||||
case "string":
|
||||
case "double":
|
||||
case "bool":
|
||||
case "int":
|
||||
push(signature, k + "-" + replace(args[k], /[^A-Za-z0-9_-]+/g, "_"));
|
||||
break;
|
||||
|
||||
default:
|
||||
push(signature, type(args[k]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let candidates = [];
|
||||
|
||||
for (let i = length(signature); i > 0; i--) {
|
||||
let path = sprintf("ubus/%s.json", join("~", signature)),
|
||||
mock = mocklib.read_json_file(path);
|
||||
|
||||
if (mock != mock) {
|
||||
self._error = "Invalid argument";
|
||||
|
||||
return null;
|
||||
}
|
||||
else if (mock) {
|
||||
mocklib.trace_call("ctx", "call", { object, method, args });
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
push(candidates, path);
|
||||
pop(signature);
|
||||
}
|
||||
|
||||
// Return null silently for unmatched calls (non-critical in tests)
|
||||
self._error = "Method not found";
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
disconnect: () => null,
|
||||
|
||||
error: () => self.error()
|
||||
};
|
||||
},
|
||||
|
||||
error: function() {
|
||||
let e = this._error;
|
||||
delete this._error;
|
||||
|
||||
return e;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,213 @@
|
||||
// UCI mock for adblock-fast tests.
|
||||
// Based on mwan4's UCI mock, extended with set/save/commit/changes/delete.
|
||||
|
||||
let mocklib = global.mocklib; // ucode-lsp disable
|
||||
|
||||
let byte = (str, off) => { // ucode-lsp disable
|
||||
let v = ord(str, off);
|
||||
return length(v) ? v[0] : v;
|
||||
};
|
||||
|
||||
let hash = (s) => { // ucode-lsp disable
|
||||
let h = 7;
|
||||
|
||||
for (let i = 0; i < length(s); i++)
|
||||
h = h * 31 + byte(s, i);
|
||||
|
||||
return h;
|
||||
};
|
||||
|
||||
let id = (config, t, n) => { // ucode-lsp disable
|
||||
while (true) {
|
||||
let id = sprintf('cfg%08x', hash(t + n));
|
||||
|
||||
if (!exists(config, id))
|
||||
return id;
|
||||
|
||||
n++;
|
||||
}
|
||||
};
|
||||
|
||||
let fixup_config = (config) => { // ucode-lsp disable
|
||||
let rv = {};
|
||||
let n_section = 0;
|
||||
|
||||
for (let stype in config) {
|
||||
switch (type(config[stype])) {
|
||||
case 'object':
|
||||
config[stype] = [ config[stype] ];
|
||||
/* fall through */
|
||||
|
||||
case 'array':
|
||||
for (let idx, sobj in config[stype]) {
|
||||
let sid, anon;
|
||||
|
||||
if (exists(sobj, '.name') && !exists(rv, sobj['.name'])) {
|
||||
sid = sobj['.name'];
|
||||
anon = false;
|
||||
}
|
||||
else {
|
||||
sid = id(rv, stype, idx);
|
||||
anon = true;
|
||||
}
|
||||
|
||||
rv[sid] = {
|
||||
'.index': n_section++,
|
||||
...sobj,
|
||||
'.name': sid,
|
||||
'.type': stype,
|
||||
'.anonymous': anon
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let n, sid in sort(keys(rv), (a, b) => rv[a]['.index'] - rv[b]['.index']))
|
||||
rv[sid]['.index'] = n;
|
||||
|
||||
return rv;
|
||||
};
|
||||
|
||||
return {
|
||||
cursor: () => ({
|
||||
_configs: {},
|
||||
_dirty: {},
|
||||
|
||||
load: function(file) {
|
||||
let basename = replace(file, /^.+\//, ''),
|
||||
path = sprintf("uci/%s.json", basename),
|
||||
mock = mocklib.read_json_file(path);
|
||||
|
||||
if (!mock || mock != mock) {
|
||||
mocklib.I("No configuration fixture defined for uci package %s.", file);
|
||||
mocklib.I("Provide a mock configuration through the following JSON file:\n%s\n", path);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
this._configs[basename] = fixup_config(mock);
|
||||
},
|
||||
|
||||
_get_section: function(config, section) {
|
||||
if (!exists(this._configs, config)) {
|
||||
this.load(config);
|
||||
|
||||
if (!exists(this._configs, config))
|
||||
return null;
|
||||
}
|
||||
|
||||
let cfg = this._configs[config],
|
||||
extended = match(section, "^@([A-Za-z0-9_-]+)\\[(-?[0-9]+)\\]$");
|
||||
|
||||
if (extended) {
|
||||
let stype = extended[1],
|
||||
sindex = +extended[2];
|
||||
|
||||
let sids = sort(
|
||||
filter(keys(cfg), sid => cfg[sid]['.type'] == stype),
|
||||
(a, b) => cfg[a]['.index'] - cfg[b]['.index']
|
||||
);
|
||||
|
||||
if (sindex < 0)
|
||||
sindex = sids.length + sindex;
|
||||
|
||||
return cfg[sids[sindex]];
|
||||
}
|
||||
|
||||
return cfg[section];
|
||||
},
|
||||
|
||||
get: function(config, section, option) {
|
||||
let sobj = this._get_section(config, section);
|
||||
|
||||
if (option && index(option, ".") == 0)
|
||||
return null;
|
||||
else if (sobj && option)
|
||||
return sobj[option];
|
||||
else if (sobj)
|
||||
return sobj[".type"];
|
||||
},
|
||||
|
||||
get_all: function(config, section) {
|
||||
return section ? this._get_section(config, section) : this._configs[config];
|
||||
},
|
||||
|
||||
foreach: function(config, stype, cb) {
|
||||
let rv = false;
|
||||
|
||||
if (!exists(this._configs, config))
|
||||
this.load(config);
|
||||
|
||||
if (exists(this._configs, config)) {
|
||||
let cfg = this._configs[config],
|
||||
sids = sort(keys(cfg), (a, b) => cfg[a]['.index'] - cfg[b]['.index']);
|
||||
|
||||
for (let i, sid in sids) {
|
||||
if (stype == null || cfg[sid]['.type'] == stype) {
|
||||
if (cb({ ...(cfg[sid]) }) === false)
|
||||
break;
|
||||
|
||||
rv = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
},
|
||||
|
||||
// -- Extensions for adblock-fast --
|
||||
|
||||
set: function(config, section, option, value) {
|
||||
let sobj = this._get_section(config, section);
|
||||
if (sobj) {
|
||||
sobj[option] = value;
|
||||
this._dirty[config] = true;
|
||||
}
|
||||
},
|
||||
|
||||
save: function(config) {
|
||||
return true;
|
||||
},
|
||||
|
||||
commit: function(config) {
|
||||
delete this._dirty[config];
|
||||
return true;
|
||||
},
|
||||
|
||||
changes: function(config) {
|
||||
return this._dirty[config] ? [['set']] : [];
|
||||
},
|
||||
|
||||
revert: function(config) {
|
||||
delete this._dirty[config];
|
||||
},
|
||||
|
||||
delete: function(config, section, option) {
|
||||
if (option) {
|
||||
let sobj = this._get_section(config, section);
|
||||
if (sobj) delete sobj[option];
|
||||
} else if (section) {
|
||||
if (exists(this._configs, config))
|
||||
delete this._configs[config][section];
|
||||
}
|
||||
},
|
||||
|
||||
list_add: function(config, section, option, value) {
|
||||
let sobj = this._get_section(config, section);
|
||||
if (!sobj) return;
|
||||
if (type(sobj[option]) != 'array')
|
||||
sobj[option] = sobj[option] ? [sobj[option]] : [];
|
||||
push(sobj[option], value);
|
||||
this._dirty[config] = true;
|
||||
},
|
||||
|
||||
list_remove: function(config, section, option, value) {
|
||||
let sobj = this._get_section(config, section);
|
||||
if (!sobj || type(sobj[option]) != 'array') return;
|
||||
sobj[option] = filter(sobj[option], v => v != value);
|
||||
this._dirty[config] = true;
|
||||
},
|
||||
})
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"up": true,
|
||||
"pending": false,
|
||||
"available": true,
|
||||
"autostart": true,
|
||||
"device": "eth0",
|
||||
"l3_device": "eth0",
|
||||
"proto": "dhcp",
|
||||
"ipv4-address": [
|
||||
{
|
||||
"address": "192.168.1.100",
|
||||
"mask": 24
|
||||
}
|
||||
],
|
||||
"route": [
|
||||
{
|
||||
"target": "0.0.0.0",
|
||||
"mask": 0,
|
||||
"nexthop": "192.168.1.1",
|
||||
"source": "192.168.1.100/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"interface": [
|
||||
{
|
||||
"interface": "loopback",
|
||||
"up": true,
|
||||
"device": "lo",
|
||||
"l3_device": "lo",
|
||||
"route": []
|
||||
},
|
||||
{
|
||||
"interface": "wan",
|
||||
"up": true,
|
||||
"device": "eth0",
|
||||
"l3_device": "eth0",
|
||||
"route": [
|
||||
{
|
||||
"target": "0.0.0.0",
|
||||
"mask": 0,
|
||||
"nexthop": "192.168.1.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"dnsmasq": {
|
||||
"instances": {
|
||||
"cfg01411c": {
|
||||
"running": true,
|
||||
"pid": 1234,
|
||||
"command": [ "/usr/sbin/dnsmasq", "-C", "/var/etc/dnsmasq.conf.cfg01411c", "-k", "-x", "/var/run/dnsmasq/dnsmasq.cfg01411c.pid" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"memory": {
|
||||
"total": 536870912,
|
||||
"available": 268435456,
|
||||
"free": 134217728,
|
||||
"cached": 67108864,
|
||||
"buffered": 33554432,
|
||||
"shared": 4194304
|
||||
},
|
||||
"swap": {
|
||||
"total": 0,
|
||||
"free": 0
|
||||
},
|
||||
"uptime": 86400,
|
||||
"localtime": 1615382640,
|
||||
"load": [ 0, 0, 0 ]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"config": [
|
||||
{
|
||||
".name": "config",
|
||||
".type": "config",
|
||||
"enabled": "1",
|
||||
"dns": "dnsmasq.servers",
|
||||
"verbosity": "2",
|
||||
"force_dns": "0",
|
||||
"compressed_cache": "0",
|
||||
"config_update_enabled": "0",
|
||||
"ipv6_enabled": "0",
|
||||
"canary_domains_icloud": "0",
|
||||
"canary_domains_mozilla": "0",
|
||||
"dnsmasq_sanity_check": "0",
|
||||
"dnsmasq_validity_check": "0",
|
||||
"parallel_downloads": "0",
|
||||
"allow_non_ascii": "0",
|
||||
"update_config_sizes": "0",
|
||||
"heartbeat_domain": "-",
|
||||
"download_timeout": "10",
|
||||
"curl_retry": "1",
|
||||
"compressed_cache_dir": "TESTDIR/cache"
|
||||
}
|
||||
],
|
||||
"file_url": [
|
||||
{
|
||||
".name": "blocked_domains",
|
||||
".type": "file_url",
|
||||
"enabled": "1",
|
||||
"url": "file://TESTDIR/data/domains.txt",
|
||||
"action": "block",
|
||||
"name": "Test Domains"
|
||||
},
|
||||
{
|
||||
".name": "blocked_hosts",
|
||||
".type": "file_url",
|
||||
"enabled": "1",
|
||||
"url": "file://TESTDIR/data/hosts.txt",
|
||||
"action": "block",
|
||||
"name": "Test Hosts"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"dnsmasq": [
|
||||
{
|
||||
".name": "cfg01411c",
|
||||
".type": "dnsmasq",
|
||||
".anonymous": true,
|
||||
"domainneeded": "1",
|
||||
"localise_queries": "1",
|
||||
"rebind_protection": "1",
|
||||
"rebind_localhost": "1",
|
||||
"local": "/lan/",
|
||||
"domain": "lan",
|
||||
"expandhosts": "1",
|
||||
"cachesize": "1000",
|
||||
"authoritative": "1",
|
||||
"readethers": "1",
|
||||
"leasefile": "/tmp/dhcp.leases",
|
||||
"resolvfile": "/tmp/resolv.conf.d/resolv.conf.auto",
|
||||
"localservice": "1",
|
||||
"ednspacket_max": "1232"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"interface": [
|
||||
{
|
||||
".name": "loopback",
|
||||
".type": "interface",
|
||||
"device": "lo",
|
||||
"proto": "static",
|
||||
"ipaddr": "127.0.0.1",
|
||||
"netmask": "255.0.0.0"
|
||||
},
|
||||
{
|
||||
".name": "wan",
|
||||
".type": "interface",
|
||||
"device": "eth0",
|
||||
"proto": "dhcp"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"smartdns": [
|
||||
{
|
||||
".name": "smartdns",
|
||||
".type": "smartdns",
|
||||
"enabled": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"unbound": [
|
||||
{
|
||||
".name": "unbound",
|
||||
".type": "unbound",
|
||||
"enabled": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Functional test runner for adblock-fast.
|
||||
#
|
||||
# Adapts the mwan4 mock-and-expect pattern for adblock-fast:
|
||||
# - Patches ES module imports to require() calls
|
||||
# - Redirects hardcoded paths to a temp directory
|
||||
# - Exports internal functions for test access
|
||||
# - Uses real shell commands (sed/sort/grep/awk) with mock UCI/UBus
|
||||
#
|
||||
# Usage: cd source.openwrt.melmac.ca/adblock-fast && bash tests/run_tests.sh [test_file...]
|
||||
|
||||
set -o pipefail
|
||||
|
||||
line='........................................'
|
||||
|
||||
# ── Temp directories ─────────────────────────────────────────────────
|
||||
|
||||
TESTDIR="/tmp/adb_test.$$"
|
||||
patch_dir="/tmp/adb_test_modules.$$"
|
||||
stub_dir="$TESTDIR/stubs"
|
||||
|
||||
mkdir -p "$TESTDIR"/{var_run/adblock-fast,var,shm,var_lib_unbound,etc,cache,tmp}
|
||||
mkdir -p "$patch_dir"
|
||||
mkdir -p "$stub_dir"
|
||||
|
||||
trap "rm -rf '$TESTDIR' '$patch_dir'" EXIT
|
||||
|
||||
# ── Copy test data ───────────────────────────────────────────────────
|
||||
|
||||
cp -r ./tests/data "$TESTDIR/data"
|
||||
|
||||
# ── Prepare resolved mock fixtures (replace TESTDIR placeholder) ─────
|
||||
|
||||
mkdir -p "$TESTDIR/mocks_resolved/uci" "$TESTDIR/mocks_resolved/ubus"
|
||||
for f in ./tests/mocks/uci/*.json; do
|
||||
sed "s|TESTDIR|$TESTDIR|g" "$f" > "$TESTDIR/mocks_resolved/uci/$(basename "$f")"
|
||||
done
|
||||
for f in ./tests/mocks/ubus/*.json; do
|
||||
cp "$f" "$TESTDIR/mocks_resolved/ubus/$(basename "$f")"
|
||||
done
|
||||
|
||||
# ── Create resolver stubs ───────────────────────────────────────────
|
||||
|
||||
cat > "$stub_dir/dnsmasq" << 'STUB'
|
||||
#!/bin/sh
|
||||
case "$1" in
|
||||
--version)
|
||||
echo "Dnsmasq version 2.89"
|
||||
echo "Compile time options: IPv6 GNU-getopt no-DBus no-UBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP no-conntrack ipset nftset auth no-cryptohash no-DNSSEC loop-detect inotify dumpfile"
|
||||
;;
|
||||
--test)
|
||||
echo "dnsmasq: syntax check OK."
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
STUB
|
||||
chmod +x "$stub_dir/dnsmasq"
|
||||
|
||||
for cmd in smartdns unbound; do
|
||||
printf '#!/bin/sh\nexit 0\n' > "$stub_dir/$cmd"
|
||||
chmod +x "$stub_dir/$cmd"
|
||||
done
|
||||
|
||||
# Create ipset/nft stubs
|
||||
for cmd in ipset nft; do
|
||||
printf '#!/bin/sh\nexit 0\n' > "$stub_dir/$cmd"
|
||||
chmod +x "$stub_dir/$cmd"
|
||||
done
|
||||
|
||||
# Create resolveip stub
|
||||
cat > "$stub_dir/resolveip" << 'STUB'
|
||||
#!/bin/sh
|
||||
echo "127.0.0.1"
|
||||
exit 0
|
||||
STUB
|
||||
chmod +x "$stub_dir/resolveip"
|
||||
|
||||
# ── Patch adblock-fast.uc ───────────────────────────────────────────
|
||||
|
||||
# The sed pipeline:
|
||||
# 1. Convert ES module imports to require() calls
|
||||
# 2. Redirect hardcoded paths to TESTDIR
|
||||
# 3. Extend is_present() search paths with stub_dir
|
||||
# 4. Export internal test helpers
|
||||
|
||||
sed \
|
||||
-e "s|import { readfile, writefile, popen, stat, unlink, rename, open, glob, mkdir, mkstemp, symlink, chmod, chown, realpath, lsdir, access, dirname } from 'fs';|let _fs = require('fs'), readfile = _fs.readfile, writefile = _fs.writefile, popen = _fs.popen, stat = _fs.stat, unlink = _fs.unlink, rename = _fs.rename, open = _fs.open, glob = _fs.glob, mkdir = _fs.mkdir, mkstemp = _fs.mkstemp, symlink = _fs.symlink, chmod = _fs.chmod, chown = _fs.chown, realpath = _fs.realpath, lsdir = _fs.lsdir, access = _fs.access, dirname = _fs.dirname;|" \
|
||||
-e "s|import { cursor } from 'uci';|let _uci = require('uci'), cursor = _uci.cursor;|" \
|
||||
-e "s|import { connect } from 'ubus';|let _ubus = require('ubus'), connect = _ubus.connect;|" \
|
||||
-e "s|dnsmasq_file: '/var/run/adblock-fast/adblock-fast.dnsmasq'|dnsmasq_file: '${TESTDIR}/var_run/adblock-fast/adblock-fast.dnsmasq'|" \
|
||||
-e "s|config_file: '/etc/config/adblock-fast'|config_file: '${TESTDIR}/etc/adblock-fast'|" \
|
||||
-e "s|run_file: '/dev/shm/adblock-fast'|run_file: '${TESTDIR}/shm/adblock-fast'|" \
|
||||
-e "s|status_file: '/dev/shm/adblock-fast.status.json'|status_file: '${TESTDIR}/shm/adblock-fast.status.json'|" \
|
||||
-e "s|'/var/run/' + pkg.name|'${TESTDIR}/var_run/' + pkg.name|g" \
|
||||
-e "s|'/var/lib/unbound/adb_list.' + pkg.name|'${TESTDIR}/var_run/' + pkg.name + '/adb_list.' + pkg.name|g" \
|
||||
-e "s|'/var/' + pkg.name|'${TESTDIR}/var/' + pkg.name|g" \
|
||||
-e "s|for (let dir in \['/usr/sbin', '/usr/bin', '/sbin', '/bin'\])|for (let dir in ['${stub_dir}', '/usr/sbin', '/usr/bin', '/sbin', '/bin'])|" \
|
||||
-e "s|stat('/etc/config/dhcp')|stat('${TESTDIR}/etc/dhcp')|g" \
|
||||
-e "s|stat('/etc/config/smartdns')|stat('${TESTDIR}/etc/smartdns')|g" \
|
||||
./files/lib/adblock-fast/adblock-fast.uc > "$patch_dir/adblock-fast.uc"
|
||||
|
||||
# Append test-helper exports to the patched module.
|
||||
# We add a _test_internals object that gives tests access to module-private state.
|
||||
# NOTE: cfg is accessed via get_cfg()/set_cfg() because env.load_config()
|
||||
# reassigns cfg, which would make a direct reference stale.
|
||||
sed -i '/^export default {/,/^};/{
|
||||
/process_file_url,/a\
|
||||
\t// Test helpers (injected by test runner)\
|
||||
\t_test_internals: {\
|
||||
\t\tdownload_lists: download_lists,\
|
||||
\t\tdetect_file_type: detect_file_type,\
|
||||
\t\tdns_modes: dns_modes,\
|
||||
\t\tget_cfg: function() { return cfg; },\
|
||||
\t\tset_cfg: function(k, v) { cfg[k] = v; },\
|
||||
\t\tstate: state,\
|
||||
\t\tenv: env,\
|
||||
\t\tdns_output: dns_output,\
|
||||
\t\tstatus_data: status_data,\
|
||||
\t\tlist_formats: list_formats,\
|
||||
\t\ttmp: tmp,\
|
||||
\t\tappend_urls: append_urls,\
|
||||
\t\tcount_lines: count_lines,\
|
||||
\t\tcount_blocked_domains: count_blocked_domains,\
|
||||
\t},
|
||||
}' "$patch_dir/adblock-fast.uc"
|
||||
|
||||
# Patch cli.uc too (for tests that exercise the CLI path)
|
||||
sed \
|
||||
-e "s|import adb from 'adblock-fast';|let adb = require('adblock-fast');|" \
|
||||
./files/lib/adblock-fast/cli.uc > "$patch_dir/cli.uc"
|
||||
|
||||
# ── Set up environment ───────────────────────────────────────────────
|
||||
|
||||
export TMPDIR="$TESTDIR/tmp"
|
||||
export PATH="$stub_dir:$PATH"
|
||||
|
||||
# ucode invocation: patched module first, then mocklib, then original source
|
||||
ucode="ucode -S -L$patch_dir -L./tests/lib -L./files/lib/adblock-fast"
|
||||
|
||||
# ── Test framework (adapted from mwan4) ──────────────────────────────
|
||||
|
||||
extract_sections() {
|
||||
local file=$1
|
||||
local dir=$2
|
||||
local count=0
|
||||
local tag line outfile
|
||||
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
"-- Testcase --")
|
||||
tag="test"
|
||||
count=$((count + 1))
|
||||
outfile=$(printf "%s/%03d.in" "$dir" $count)
|
||||
printf "" > "$outfile"
|
||||
;;
|
||||
"-- Environment --")
|
||||
tag="env"
|
||||
count=$((count + 1))
|
||||
outfile=$(printf "%s/%03d.env" "$dir" $count)
|
||||
printf "" > "$outfile"
|
||||
;;
|
||||
"-- Expect stdout --"|"-- Expect stderr --"|"-- Expect exitcode --")
|
||||
tag="${line#-- Expect }"
|
||||
tag="${tag% --}"
|
||||
count=$((count + 1))
|
||||
outfile=$(printf "%s/%03d.%s" "$dir" $count "$tag")
|
||||
printf "" > "$outfile"
|
||||
;;
|
||||
"-- File "*" --")
|
||||
tag="file"
|
||||
outfile="${line#-- File }"
|
||||
outfile="$(echo "${outfile% --}" | xargs)"
|
||||
outfile="$dir/files$(readlink -m "/${outfile:-file}")"
|
||||
mkdir -p "$(dirname "$outfile")"
|
||||
printf "" > "$outfile"
|
||||
;;
|
||||
"-- End --")
|
||||
tag=""
|
||||
outfile=""
|
||||
;;
|
||||
*)
|
||||
if [ -n "$tag" ]; then
|
||||
printf "%s\\n" "$line" >> "$outfile"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done < "$file"
|
||||
|
||||
# Post-process: replace TESTDIR placeholder in extracted files
|
||||
# - files/ directory (mock data)
|
||||
# - expect sections (.stdout, .stderr) so tests can reference TESTDIR paths
|
||||
# NOTE: Do NOT substitute in .in files — those use TESTDIR as a ucode global variable
|
||||
find "$dir/files" -type f 2>/dev/null | while read -r f; do
|
||||
sed -i "s|TESTDIR|$TESTDIR|g" "$f"
|
||||
done
|
||||
for f in "$dir"/*.stdout "$dir"/*.stderr; do
|
||||
[ -f "$f" ] && sed -i "s|TESTDIR|$TESTDIR|g" "$f"
|
||||
done
|
||||
|
||||
return $(ls -l "$dir/"*.in 2>/dev/null | wc -l)
|
||||
}
|
||||
|
||||
run_testcase() {
|
||||
local num=$1
|
||||
local dir=$2
|
||||
local in=$3
|
||||
local env=$4
|
||||
local out=$5
|
||||
local err=$6
|
||||
local code=$7
|
||||
local fail=0
|
||||
|
||||
# Clean test state between runs
|
||||
rm -rf "$TESTDIR"/var_run/adblock-fast/*
|
||||
rm -f "$TESTDIR"/var/adblock-fast.*
|
||||
rm -f "$TESTDIR"/shm/adblock-fast*
|
||||
rm -f "$TESTDIR"/var_lib_unbound/*
|
||||
rm -f "$TESTDIR"/tmp/adblock-fast*
|
||||
mkdir -p "$TESTDIR"/var_run/adblock-fast
|
||||
|
||||
$ucode \
|
||||
-D MOCK_SEARCH_PATH='["'"$dir"'/files", "'"$TESTDIR"'/mocks_resolved", "./tests/mocks"]' \
|
||||
-D TESTDIR='"'"$TESTDIR"'"' \
|
||||
${env:+-F "$env"} \
|
||||
-l mocklib \
|
||||
- <"$in" >"$dir/res.out" 2>"$dir/res.err"
|
||||
|
||||
printf "%d\n" $? > "$dir/res.code"
|
||||
|
||||
touch "$dir/empty"
|
||||
|
||||
if ! cmp -s "$dir/res.err" "${err:-$dir/empty}"; then
|
||||
[ $fail = 0 ] && printf "!\n"
|
||||
printf "Testcase #%d: Expected stderr did not match:\n" $num
|
||||
diff -u --color=always --label="Expected stderr" --label="Resulting stderr" "${err:-$dir/empty}" "$dir/res.err"
|
||||
printf -- "---\n"
|
||||
fail=1
|
||||
fi
|
||||
|
||||
if ! cmp -s "$dir/res.out" "${out:-$dir/empty}"; then
|
||||
[ $fail = 0 ] && printf "!\n"
|
||||
printf "Testcase #%d: Expected stdout did not match:\n" $num
|
||||
diff -u --color=always --label="Expected stdout" --label="Resulting stdout" "${out:-$dir/empty}" "$dir/res.out"
|
||||
printf -- "---\n"
|
||||
fail=1
|
||||
fi
|
||||
|
||||
if [ -n "$code" ] && ! cmp -s "$dir/res.code" "$code"; then
|
||||
[ $fail = 0 ] && printf "!\n"
|
||||
printf "Testcase #%d: Expected exit code did not match:\n" $num
|
||||
diff -u --color=always --label="Expected code" --label="Resulting code" "$code" "$dir/res.code"
|
||||
printf -- "---\n"
|
||||
fail=1
|
||||
fi
|
||||
|
||||
return $fail
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local file=$1
|
||||
local name=${file##*/}
|
||||
local res ecode eout eerr ein eenv tests
|
||||
local testcase_first=0 failed=0 count=0
|
||||
|
||||
printf "%s %s " "$name" "${line:${#name}}"
|
||||
|
||||
mkdir "/tmp/test.$$"
|
||||
|
||||
extract_sections "$file" "/tmp/test.$$"
|
||||
tests=$?
|
||||
|
||||
[ -f "/tmp/test.$$/001.in" ] && testcase_first=1
|
||||
|
||||
for res in "/tmp/test.$$/"[0-9]*; do
|
||||
case "$res" in
|
||||
*.in)
|
||||
count=$((count + 1))
|
||||
|
||||
if [ $testcase_first = 1 ]; then
|
||||
# Flush previous test
|
||||
if [ -n "$ein" ]; then
|
||||
run_testcase $count "/tmp/test.$$" "$ein" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1))
|
||||
|
||||
eout=""
|
||||
eerr=""
|
||||
ecode=""
|
||||
eenv=""
|
||||
fi
|
||||
|
||||
ein=$res
|
||||
else
|
||||
run_testcase $count "/tmp/test.$$" "$res" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1))
|
||||
|
||||
eout=""
|
||||
eerr=""
|
||||
ecode=""
|
||||
eenv=""
|
||||
fi
|
||||
|
||||
;;
|
||||
*.env) eenv=$res ;;
|
||||
*.stdout) eout=$res ;;
|
||||
*.stderr) eerr=$res ;;
|
||||
*.exitcode) ecode=$res ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Flush last test
|
||||
if [ $testcase_first = 1 ] && [ -n "$ein" ]; then
|
||||
run_testcase $count "/tmp/test.$$" "$ein" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1))
|
||||
fi
|
||||
|
||||
rm -r "/tmp/test.$$"
|
||||
|
||||
if [ $failed = 0 ]; then
|
||||
printf "OK\n"
|
||||
else
|
||||
printf "%s %s FAILED (%d/%d)\n" "$name" "${line:${#name}}" $failed $tests
|
||||
fi
|
||||
|
||||
return $failed
|
||||
}
|
||||
|
||||
|
||||
n_tests=0
|
||||
n_fails=0
|
||||
|
||||
select_tests="$@"
|
||||
|
||||
use_test() {
|
||||
local input="$(readlink -f "$1")"
|
||||
local test
|
||||
|
||||
[ -f "$input" ] || return 1
|
||||
[ -n "$select_tests" ] || return 0
|
||||
|
||||
for test in $select_tests; do
|
||||
test="$(readlink -f "$test")"
|
||||
|
||||
[ "$test" != "$input" ] || return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
for catdir in tests/[0-9][0-9]_*; do
|
||||
[ -d "$catdir" ] || continue
|
||||
|
||||
printf "\n##\n## Running %s tests\n##\n\n" "${catdir##*/[0-9][0-9]_}"
|
||||
|
||||
for testfile in "$catdir/"[0-9][0-9]_*; do
|
||||
use_test "$testfile" || continue
|
||||
|
||||
n_tests=$((n_tests + 1))
|
||||
run_test "$testfile" || n_fails=$((n_fails + 1))
|
||||
done
|
||||
done
|
||||
|
||||
# ── Shell script syntax checks ──────────────────────────────────────
|
||||
|
||||
printf "\n##\n## Checking shell script syntax\n##\n\n"
|
||||
for shellscript in \
|
||||
files/etc/init.d/* \
|
||||
files/etc/uci-defaults/*; do
|
||||
[ -f "$shellscript" ] || continue
|
||||
head -1 "$shellscript" | grep -q '^#!/bin/sh' || continue
|
||||
name="${shellscript#files/}"
|
||||
n_tests=$((n_tests + 1))
|
||||
printf "%s %s " "$name" "${line:${#name}}"
|
||||
if sh -n "$shellscript" 2>/dev/null; then
|
||||
printf "OK\n"
|
||||
else
|
||||
printf "FAIL\n"
|
||||
sh -n "$shellscript"
|
||||
n_fails=$((n_fails + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
printf "\nRan %d tests, %d okay, %d failures\n" $n_tests $((n_tests - n_fails)) $n_fails
|
||||
exit $n_fails
|
||||
Reference in New Issue
Block a user