summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÁrni Dagur <arni@dagur.eu>2020-07-27 20:35:50 +0000
committerGitHub <noreply@github.com>2020-07-27 20:35:50 +0000
commit25684535e85a8bbb05b65b0da2304273d1d90ca0 (patch)
treeaad63588ac9eafe99ddd0d3ed847a3726d947271
parentdc35e6044c249261dfc46eff831d5885a993eb0f (diff)
Update upstream library to 0.3.0, respond to API changes (#10)
* Update upstream library to 0.3.0, respond to API changes * Change pypi publishing criteria in CI * Create test to make sure the version numbers are the same everywhere
-rw-r--r--.github/workflows/ci.yml7
-rw-r--r--Cargo.toml4
-rw-r--r--adblock/adblock.pyi28
-rw-r--r--pyproject.toml3
-rw-r--r--src/lib.rs150
-rw-r--r--tests/test_engine.py48
-rw-r--r--tests/test_typestubs.py53
-rw-r--r--tests/test_version_numbers.py28
8 files changed, 249 insertions, 72 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 18cdc06..4071231 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -73,8 +73,13 @@ jobs:
73 run: poetry run maturin develop --release 73 run: poetry run maturin develop --release
74 74
75 - name: Run Python tests 75 - name: Run Python tests
76 if: matrix.python-version != 3.5
76 run: poetry run pytest -vv --color=yes 77 run: poetry run pytest -vv --color=yes
77 78
79 - name: Run Python tests (skip typestub tests)
80 if: matrix.python-version == 3.5
81 run: poetry run pytest -vv --color=yes --ignore=tests/test_typestubs.py
82
78 python-publish: 83 python-publish:
79 needs: build 84 needs: build
80 runs-on: ${{ matrix.os }} 85 runs-on: ${{ matrix.os }}
@@ -138,7 +143,7 @@ jobs:
138 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 143 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
139 144
140 - name: PyPi publish 145 - name: PyPi publish
141 if: startsWith(github.ref, 'refs/tags/') 146 if: github.event_name == 'release' && github.event.action == 'created'
142 env: 147 env:
143 MATURIN_PASSWORD: ${{ secrets.PYPI }} 148 MATURIN_PASSWORD: ${{ secrets.PYPI }}
144 run: poetry run maturin publish --interpreter python${{matrix.python_version}} --username __token__ 149 run: poetry run maturin publish --interpreter python${{matrix.python_version}} --username __token__
diff --git a/Cargo.toml b/Cargo.toml
index acba2c9..4af9670 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
1[package] 1[package]
2name = "adblock" 2name = "adblock"
3publish = false 3publish = false
4version = "0.2.3" 4version = "0.3.0"
5authors = ["Árni Dagur <arni@dagur.eu"] 5authors = ["Árni Dagur <arni@dagur.eu"]
6edition = "2018" 6edition = "2018"
7license = "MIT OR Apache-2.0" 7license = "MIT OR Apache-2.0"
@@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0"
10debug = true 10debug = true
11 11
12[dependencies] 12[dependencies]
13adblock = "0.2.9" 13adblock = "0.3.0"
14pyo3 = "0.10" 14pyo3 = "0.10"
15 15
16[lib] 16[lib]
diff --git a/adblock/adblock.pyi b/adblock/adblock.pyi
index 50a61e8..82f753b 100644
--- a/adblock/adblock.pyi
+++ b/adblock/adblock.pyi
@@ -11,7 +11,7 @@ class BlockerResult:
11 def __repr__(self) -> str: 11 def __repr__(self) -> str:
12 pass 12 pass
13 13
14class HostnameSpecificResources: 14class UrlSpecificResources:
15 hide_selectors: Set[str] 15 hide_selectors: Set[str]
16 style_selectors: Dict[str, List[str]] 16 style_selectors: Dict[str, List[str]]
17 exceptions: Set[str] 17 exceptions: Set[str]
@@ -19,14 +19,16 @@ class HostnameSpecificResources:
19 def __repr__(self) -> str: 19 def __repr__(self) -> str:
20 pass 20 pass
21 21
22class FilterSet:
23 def __init__(self, debug: bool = False) -> None:
24 pass
25 def add_filter_list(self, filter_list: str, format: str) -> None:
26 pass
27 def add_filters(self, filters: List[str], format: str) -> None:
28 pass
29
22class Engine: 30class Engine:
23 def __init__( 31 def __init__(self, filter_set: FilterSet, optimize: bool = True) -> None:
24 self,
25 network_filters: Optional[List[str]] = None,
26 load_network: bool = True,
27 load_cosmetic: bool = False,
28 debug: bool = False,
29 ) -> None:
30 pass 32 pass
31 def check_network_urls( 33 def check_network_urls(
32 self, url: str, source_url: str, request_type: str 34 self, url: str, source_url: str, request_type: str
@@ -60,17 +62,17 @@ class Engine:
60 pass 62 pass
61 def deserialize_from_file(self, file: str) -> None: 63 def deserialize_from_file(self, file: str) -> None:
62 pass 64 pass
63 def add_filter_list(self, filter_list: str) -> None:
64 pass
65 def filter_exists(self, filter: str) -> bool: 65 def filter_exists(self, filter: str) -> bool:
66 pass 66 pass
67 def tags_enable(self, tags: List[str]) -> None: 67 def use_tags(self, tags: List[str]) -> None:
68 pass
69 def enable_tags(self, tags: List[str]) -> None:
68 pass 70 pass
69 def tags_disable(self, tags: List[str]) -> None: 71 def disable_tags(self, tags: List[str]) -> None:
70 pass 72 pass
71 def tag_exists(self, tag: str) -> bool: 73 def tag_exists(self, tag: str) -> bool:
72 pass 74 pass
73 def hostname_cosmetic_resources(self, hostname: str) -> HostnameSpecificResources: 75 def url_cosmetic_resources(self, url: str) -> UrlSpecificResources:
74 pass 76 pass
75 def hidden_class_id_selectors( 77 def hidden_class_id_selectors(
76 self, classes: List[str], ids: List[str], exceptions: Set[str] 78 self, classes: List[str], ids: List[str], exceptions: Set[str]
diff --git a/pyproject.toml b/pyproject.toml
index d92a78f..099e239 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
1[tool.poetry] 1[tool.poetry]
2name = "adblock" 2name = "adblock"
3version = "0.2.3" 3version = "0.3.0"
4description = "Brave's adblocking in Python" 4description = "Brave's adblocking in Python"
5authors = ["Árni Dagur <arni@dagur.eu>"] 5authors = ["Árni Dagur <arni@dagur.eu>"]
6license = "MIT OR Apache-2.0" 6license = "MIT OR Apache-2.0"
@@ -14,3 +14,4 @@ python = "^3.5"
14[tool.poetry.dev-dependencies] 14[tool.poetry.dev-dependencies]
15maturin = "*" 15maturin = "*"
16pytest = "*" 16pytest = "*"
17toml = "*"
diff --git a/src/lib.rs b/src/lib.rs
index 588f4e9..7839a42 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,8 +12,10 @@
12 12
13use adblock::blocker::BlockerError as RustBlockerError; 13use adblock::blocker::BlockerError as RustBlockerError;
14use adblock::blocker::BlockerResult as RustBlockerResult; 14use adblock::blocker::BlockerResult as RustBlockerResult;
15use adblock::cosmetic_filter_cache::HostnameSpecificResources as RustHostnameSpecificResources; 15use adblock::cosmetic_filter_cache::UrlSpecificResources as RustUrlSpecificResources;
16use adblock::engine::Engine as RustEngine; 16use adblock::engine::Engine as RustEngine;
17use adblock::lists::FilterFormat;
18use adblock::lists::FilterSet as RustFilterSet;
17use pyo3::class::PyObjectProtocol; 19use pyo3::class::PyObjectProtocol;
18use pyo3::exceptions::ValueError as PyValueError; 20use pyo3::exceptions::ValueError as PyValueError;
19use pyo3::prelude::*; 21use pyo3::prelude::*;
@@ -32,8 +34,9 @@ use std::io::{Read, Write};
32fn adblock(_py: Python<'_>, m: &PyModule) -> PyResult<()> { 34fn adblock(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
33 m.add("__version__", env!("CARGO_PKG_VERSION"))?; 35 m.add("__version__", env!("CARGO_PKG_VERSION"))?;
34 m.add_class::<Engine>()?; 36 m.add_class::<Engine>()?;
37 m.add_class::<FilterSet>()?;
35 m.add_class::<BlockerResult>()?; 38 m.add_class::<BlockerResult>()?;
36 m.add_class::<HostnameSpecificResources>()?; 39 m.add_class::<UrlSpecificResources>()?;
37 Ok(()) 40 Ok(())
38} 41}
39 42
@@ -163,10 +166,71 @@ impl Into<BlockerError> for RustBlockerError {
163 } 166 }
164} 167}
165 168
169fn filter_format_from_string(filter_format: &str) -> PyResult<FilterFormat> {
170 match filter_format {
171 "standard" => Ok(FilterFormat::Standard),
172 "hosts" => Ok(FilterFormat::Hosts),
173 _ => Err(PyErr::new::<PyValueError, _>("Invalid format value")),
174 }
175}
176
177/// Manages a set of rules to be added to an Engine.
178///
179/// To be able to efficiently handle special options like $badfilter, and to
180/// allow optimizations, all rules must be available when the Engine is first
181/// created. FilterSet allows assembling a compound list from multiple
182/// different sources before compiling the rules into an Engine.
183#[pyclass]
184#[text_signature = "($self, debug)"]
185#[derive(Clone)]
186pub struct FilterSet {
187 filter_set: RustFilterSet,
188}
189
190#[pymethods]
191impl FilterSet {
192 /// Creates a new `FilterSet`. The `debug` argument specifies whether or
193 /// not to save information about the original raw filter rules alongside
194 /// the more compact internal representation. If enabled, this information
195 /// will be passed to the corresponding Engine.
196 #[new]
197 #[args(debug = false)]
198 pub fn new(debug: bool) -> Self {
199 Self {
200 filter_set: RustFilterSet::new(debug),
201 }
202 }
203
204 /// Adds the contents of an entire filter list to this FilterSet. Filters
205 /// that cannot be parsed successfully are ignored.
206 ///
207 /// The format is a string containing either "standard" (ABP/uBO-style)
208 /// or "hosts".
209 #[text_signature = "($self, filter_list, format)"]
210 #[args(filter_list, format = "\"standard\"")]
211 pub fn add_filter_list(&mut self, filter_list: &str, format: &str) -> PyResult<()> {
212 let filter_format = filter_format_from_string(format)?;
213 self.filter_set.add_filter_list(filter_list, filter_format);
214 Ok(())
215 }
216
217 /// Adds a collection of filter rules to this FilterSet. Filters that
218 /// cannot be parsed successfully are ignored.
219 ///
220 /// The format is a string containing either "standard" (ABP/uBO-style)
221 /// or "hosts".
222 #[text_signature = "($self, filters, format)"]
223 #[args(filters, format = "\"standard\"")]
224 pub fn add_filters(&mut self, filters: Vec<String>, format: &str) -> PyResult<()> {
225 let filter_format = filter_format_from_string(format)?;
226 self.filter_set.add_filters(&filters, filter_format);
227 Ok(())
228 }
229}
166/// Contains cosmetic filter information intended to be injected into a 230/// Contains cosmetic filter information intended to be injected into a
167/// particular hostname. 231/// particular hostname.
168#[pyclass] 232#[pyclass]
169pub struct HostnameSpecificResources { 233pub struct UrlSpecificResources {
170 /// A set of any CSS selector on the page that should be hidden, i.e. 234 /// A set of any CSS selector on the page that should be hidden, i.e.
171 /// styled as `{ display: none !important; }`. 235 /// styled as `{ display: none !important; }`.
172 #[pyo3(get)] 236 #[pyo3(get)]
@@ -187,9 +251,9 @@ pub struct HostnameSpecificResources {
187 pub injected_script: String, 251 pub injected_script: String,
188} 252}
189 253
190impl Into<HostnameSpecificResources> for RustHostnameSpecificResources { 254impl Into<UrlSpecificResources> for RustUrlSpecificResources {
191 fn into(self) -> HostnameSpecificResources { 255 fn into(self) -> UrlSpecificResources {
192 HostnameSpecificResources { 256 UrlSpecificResources {
193 hide_selectors: self.hide_selectors, 257 hide_selectors: self.hide_selectors,
194 style_selectors: self.style_selectors, 258 style_selectors: self.style_selectors,
195 exceptions: self.exceptions, 259 exceptions: self.exceptions,
@@ -199,10 +263,10 @@ impl Into<HostnameSpecificResources> for RustHostnameSpecificResources {
199} 263}
200 264
201#[pyproto] 265#[pyproto]
202impl PyObjectProtocol for HostnameSpecificResources { 266impl PyObjectProtocol for UrlSpecificResources {
203 fn __repr__(&self) -> PyResult<String> { 267 fn __repr__(&self) -> PyResult<String> {
204 Ok(format!( 268 Ok(format!(
205 "HostnameSpecificResources<{} hide selectors, {} style selectors, {} exceptions, injected_javascript={:?}>", 269 "UrlSpecificResources<{} hide selectors, {} style selectors, {} exceptions, injected_javascript={:?}>",
206 self.hide_selectors.len(), 270 self.hide_selectors.len(),
207 self.style_selectors.len(), 271 self.style_selectors.len(),
208 self.exceptions.len(), 272 self.exceptions.len(),
@@ -231,7 +295,7 @@ impl PyObjectProtocol for HostnameSpecificResources {
231/// 295///
232/// [1]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType 296/// [1]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType
233#[pyclass] 297#[pyclass]
234#[text_signature = "($self, network_filters=None, load_network=True, load_cosmetic=False, debug=False)"] 298#[text_signature = "($self, filter_set, optimize)"]
235pub struct Engine { 299pub struct Engine {
236 engine: RustEngine, 300 engine: RustEngine,
237} 301}
@@ -240,21 +304,9 @@ pub struct Engine {
240impl Engine { 304impl Engine {
241 /// Create a new adblocking engine 305 /// Create a new adblocking engine
242 #[new] 306 #[new]
243 #[args(network_filters="None", load_network=true, load_cosmetic=false, debug=false)] 307 #[args(filter_set, optimize = true)]
244 pub fn new( 308 pub fn new(filter_set: FilterSet, optimize: bool) -> Self {
245 network_filters: Option<Vec<String>>, 309 let engine = RustEngine::from_filter_set(filter_set.filter_set, optimize);
246 load_network: bool,
247 load_cosmetic: bool,
248 debug: bool,
249 ) -> Self {
250 let filters = network_filters.unwrap_or(Vec::new());
251 let engine = RustEngine::from_rules_parametrised(
252 &filters,
253 load_network,
254 load_cosmetic,
255 debug,
256 true,
257 );
258 Self { engine } 310 Self { engine }
259 } 311 }
260 312
@@ -404,43 +456,57 @@ impl Engine {
404 self.deserialize(&data) 456 self.deserialize(&data)
405 } 457 }
406 458
407 /// Add the contents of a block list file to the blocking engine.
408 #[text_signature = "($self, filter_list)"]
409 pub fn add_filter_list(&mut self, filter_list: &str) {
410 self.engine.add_filter_list(filter_list);
411 }
412
413 /// Checks if the given filter exists in the blocking engine. 459 /// Checks if the given filter exists in the blocking engine.
414 #[text_signature = "($self, filter)"] 460 #[text_signature = "($self, filter)"]
415 pub fn filter_exists(&self, filter: &str) -> bool { 461 pub fn filter_exists(&self, filter: &str) -> bool {
416 self.engine.filter_exists(filter) 462 self.engine.filter_exists(filter)
417 } 463 }
418 464
419 /// Enable the given tags 465 /// Sets this engine's tags to be _only_ the ones provided in tags.
466 ///
467 /// Tags can be used to cheaply enable or disable network rules with a
468 /// corresponding $tag option.
420 #[text_signature = "($self, tags)"] 469 #[text_signature = "($self, tags)"]
421 pub fn tags_enable(&mut self, tags: Vec<&str>) { 470 pub fn use_tags(&mut self, tags: Vec<&str>) {
422 self.engine.tags_enable(&tags); 471 self.engine.use_tags(&tags);
423 } 472 }
424 473
425 /// Disable the given tags 474 /// Sets this engine's tags to additionally include the ones provided in
475 /// tags.
476 ///
477 /// Tags can be used to cheaply enable or disable network rules with a
478 /// corresponding $tag option.
426 #[text_signature = "($self, tags)"] 479 #[text_signature = "($self, tags)"]
427 pub fn tags_disable(&mut self, tags: Vec<&str>) { 480 pub fn enable_tags(&mut self, tags: Vec<&str>) {
428 self.engine.tags_disable(&tags); 481 self.engine.enable_tags(&tags);
429 } 482 }
430 483
431 /// Check if the given tag exists 484 /// Sets this engine's tags to no longer include the ones provided in
485 /// tags.
486 ///
487 /// Tags can be used to cheaply enable or disable network rules with a
488 /// corresponding $tag option.
489 #[text_signature = "($self, tags)"]
490 pub fn disable_tags(&mut self, tags: Vec<&str>) {
491 self.engine.disable_tags(&tags);
492 }
493
494 /// Checks if a given tag exists in this engine.
495 ///
496 /// Tags can be used to cheaply enable or disable network rules with a
497 /// corresponding $tag option.
432 #[text_signature = "($self, tag)"] 498 #[text_signature = "($self, tag)"]
433 pub fn tag_exists(&self, tag: &str) -> bool { 499 pub fn tag_exists(&self, tag: &str) -> bool {
434 self.engine.tag_exists(tag) 500 self.engine.tag_exists(tag)
435 } 501 }
436 502
437 /// Returns a set of cosmetic filter resources required for a particular 503 /// Returns a set of cosmetic filter resources required for a particular
438 /// hostname. Once this has been called, all CSS ids and classes on a 504 /// url. Once this has been called, all CSS ids and classes on a
439 /// page should be passed to hidden_class_id_selectors to obtain any 505 /// page should be passed to hidden_class_id_selectors to obtain any
440 /// stylesheets consisting of generic rules. 506 /// stylesheets consisting of generic rules.
441 #[text_signature = "($self, hostname)"] 507 #[text_signature = "($self, url)"]
442 pub fn hostname_cosmetic_resources(&self, hostname: &str) -> HostnameSpecificResources { 508 pub fn url_cosmetic_resources(&self, url: &str) -> UrlSpecificResources {
443 self.engine.hostname_cosmetic_resources(hostname).into() 509 self.engine.url_cosmetic_resources(url).into()
444 } 510 }
445 511
446 /// If any of the provided CSS classes or ids could cause a certain generic 512 /// If any of the provided CSS classes or ids could cause a certain generic
@@ -449,7 +515,7 @@ impl Engine {
449 /// referencing those classes or ids, provided that the corresponding rules 515 /// referencing those classes or ids, provided that the corresponding rules
450 /// are not excepted. 516 /// are not excepted.
451 /// 517 ///
452 /// Exceptions should be passed directly from HostnameSpecificResources. 518 /// Exceptions should be passed directly from UrlSpecificResources.
453 #[text_signature = "($self, classes, ids, exceptions)"] 519 #[text_signature = "($self, classes, ids, exceptions)"]
454 pub fn hidden_class_id_selectors( 520 pub fn hidden_class_id_selectors(
455 &self, 521 &self,
diff --git a/tests/test_engine.py b/tests/test_engine.py
index 28e25e6..a3d2898 100644
--- a/tests/test_engine.py
+++ b/tests/test_engine.py
@@ -1,40 +1,62 @@
1import adblock 1import adblock
2import pytest 2import pytest
3 3
4SMALL_FILTER_LIST = """
5||wikipedia.org^
6||old.reddit.com^
7||lobste.rs^
8"""
4 9
5def test_engine_arguments(): 10
6 # None of these should panic 11def empty_engine():
7 adblock.Engine() 12 return adblock.Engine(adblock.FilterSet())
8 adblock.Engine([]) 13
9 adblock.Engine(network_filters=None) 14
10 adblock.Engine(network_filters=[]) 15def test_engine_creation_and_blocking():
11 adblock.Engine(load_network=False, load_cosmetic=True, debug=False) 16 filter_set = adblock.FilterSet(debug=True)
12 adblock.Engine(debug=True) 17 filter_set.add_filter_list(SMALL_FILTER_LIST)
18 engine = adblock.Engine(filter_set=filter_set)
19
20 blocker_result_wikipedia = engine.check_network_urls(
21 url="https://wikipedia.org/img.png",
22 source_url="https://google.com/",
23 request_type="image",
24 )
25 assert isinstance(blocker_result_wikipedia, adblock.BlockerResult)
26 assert blocker_result_wikipedia.matched
27
28 blocker_result_facebook = engine.check_network_urls(
29 "https://facebook.com/directory/img.png",
30 "https://old.reddit.com/r/all",
31 "image",
32 )
33 assert isinstance(blocker_result_facebook, adblock.BlockerResult)
34 assert not blocker_result_facebook.matched
13 35
14 36
15def test_serde_file(tmpdir): 37def test_serde_file(tmpdir):
16 path = str(tmpdir / "cache.dat") 38 path = str(tmpdir / "cache.dat")
17 39
18 engine0 = adblock.Engine() 40 engine0 = empty_engine()
19 with pytest.raises(FileNotFoundError): 41 with pytest.raises(FileNotFoundError):
20 # We haven't created the cache.dat file, so we should get an exception 42 # We haven't created the cache.dat file, so we should get an exception
21 # when attempting to deserialize. 43 # when attempting to deserialize.
22 engine0.deserialize_from_file(path) 44 engine0.deserialize_from_file(path)
23 45
24 engine1 = adblock.Engine() 46 engine1 = empty_engine()
25 serialization_result = engine1.serialize_to_file(path) 47 serialization_result = engine1.serialize_to_file(path)
26 assert serialization_result is None 48 assert serialization_result is None
27 49
28 engine2 = adblock.Engine() 50 engine2 = empty_engine()
29 deserialization_result = engine2.deserialize_from_file(path) 51 deserialization_result = engine2.deserialize_from_file(path)
30 assert deserialization_result is None 52 assert deserialization_result is None
31 53
32 54
33def test_serde(): 55def test_serde():
34 engine = adblock.Engine() 56 engine = empty_engine()
35 serialization_result = engine.serialize() 57 serialization_result = engine.serialize()
36 assert isinstance(serialization_result, bytes) 58 assert isinstance(serialization_result, bytes)
37 59
38 engine2 = adblock.Engine() 60 engine2 = empty_engine()
39 deserialization_result = engine2.deserialize(serialization_result) 61 deserialization_result = engine2.deserialize(serialization_result)
40 assert deserialization_result is None 62 assert deserialization_result is None
diff --git a/tests/test_typestubs.py b/tests/test_typestubs.py
new file mode 100644
index 0000000..6a8d75f
--- /dev/null
+++ b/tests/test_typestubs.py
@@ -0,0 +1,53 @@
1import ast
2import re
3
4
5def read_stubfile():
6 with open("adblock/adblock.pyi", encoding="utf-8") as file:
7 node = ast.parse(file.read())
8 return node
9
10
11def get_functions_and_methods(node):
12 functions = [n for n in node.body if isinstance(n, ast.FunctionDef)]
13 classes = [n for n in node.body if isinstance(n, ast.ClassDef)]
14
15 methods = {}
16 for c in classes:
17 methods[c.name] = [n for n in c.body if isinstance(n, ast.FunctionDef)]
18
19 return functions, methods
20
21
22def pattern_exists_in_file(filename, regex):
23 """
24 Checks if the given regex is present in the given file
25 """
26 with open(filename, "r", encoding="utf-8") as f:
27 for line in f:
28 if re.search(regex, line):
29 return True
30 return False
31
32
33def test_functions_and_methods_exist_in_rust():
34 """
35 Check that for each of the functions and methods present in the Python
36 typestub file, there is a line in `src/lib.rs` containing a matching
37 definition. Since we're doing a naive grep search, without access to the
38 Rust AST, there may be false negatives.
39 """
40 stubfile_node = read_stubfile()
41 functions, methods = get_functions_and_methods(stubfile_node)
42
43 methods_flattened = []
44 for class_methods in methods.values():
45 methods_flattened += class_methods
46
47 for f in functions + methods_flattened:
48 if f.name.startswith("__"):
49 # Skip dunder methods since their names are the same for every
50 # class, making the test not particularly useful. They are also not
51 # marked `pub` in Rust.
52 continue
53 assert pattern_exists_in_file("src/lib.rs", r"pub fn {}".format(f.name))
diff --git a/tests/test_version_numbers.py b/tests/test_version_numbers.py
new file mode 100644
index 0000000..6519ffa
--- /dev/null
+++ b/tests/test_version_numbers.py
@@ -0,0 +1,28 @@
1import toml
2import adblock
3
4
5def get_version_value_poetry():
6 with open("pyproject.toml", encoding="utf-8") as f:
7 pyproject_toml = toml.loads(f.read())
8 return pyproject_toml["tool"]["poetry"]["version"]
9
10
11def get_version_value_cargo():
12 with open("Cargo.toml", encoding="utf-8") as f:
13 cargo_toml = toml.loads(f.read())
14 return cargo_toml["package"]["version"]
15
16
17def test_version_numbers_all_same():
18 """
19 Makes sure that `pyproject.toml` and `Cargo.toml` contain the same version
20 number as the one attached to the `adblock` module.
21 """
22 cargo_version = get_version_value_cargo()
23 poetry_version = get_version_value_poetry()
24 module_version = adblock.__version__
25
26 assert cargo_version == poetry_version
27 assert poetry_version == module_version
28 assert cargo_version == module_version