Skip to content

Commit

Permalink
feat(rayon): add rayon audit iterations (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
j-mendez authored Mar 24, 2024
1 parent 77696e5 commit 5eb5c9b
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 40 deletions.
28 changes: 26 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions accessibility-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ categories = ["accessibility"]
documentation = "https://docs.rs/accessibility-rs"
include = ["/src", "../LICENSE_MIT", "../LICENSE_APACHE", "../README.md", "locales"]

[features]
default = []

[dependencies]
lazy_static = { workspace = true }
accessibility-scraper = { version = "0.0.7", features = ["main"], default-features = false, path = "../accessibility-scraper" }
Expand All @@ -30,6 +27,13 @@ strum_macros = "0.25"
rust-i18n = "2"
contrast = "0.1.0"
rgb = "0.8.37"
rayon = { version = "1.10.0", optional = true }
crossbeam-channel = { version = "0.5.12", optional = true }

[features]
default = []
rayon = ["dep:rayon", "dep:crossbeam-channel"]
rayon_wasm = ["rayon/web_spin_lock"]

[dev-dependencies]
maud = "0.25.0"
Expand Down
105 changes: 105 additions & 0 deletions accessibility-rs/src/engine/audit/audit_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use crate::engine::issue::Issue;
use crate::engine::rules::rule::{Rule, Validation};
use crate::i18n::locales::get_message_i18n;

/// validate rule and push issue base
pub fn push_issue_base(
validation: Validation,
rule: &Rule,
context: &str,
lang: &str,
issues: &mut Vec<Issue>,
) {
if !validation.valid {
issues.push(Issue::new(
if !validation.message.is_empty() {
validation.message.into()
} else {
get_message_i18n(&rule, &validation.id, &lang)
},
&context,
&[
"WCAGAAA",
rule.principle.as_str(),
rule.guideline.as_str(),
&rule.rule_id.into_str(),
]
.join("."),
rule.issue_type.as_str(),
validation.elements,
));
}
}

/// validate rule and push issue
#[cfg(feature = "rayon")]
pub fn push_issue(
validation: Validation,
rule: &Rule,
context: &str,
lang: &str,
s: &crossbeam_channel::Sender<Issue>,
) {
// logic remains the same, notice the lock acquisition and pushing to the vector.
if !validation.valid {
let issue = Issue::new(
if !validation.message.is_empty() {
validation.message.into()
} else {
get_message_i18n(&rule, &validation.id, &lang)
},
&context,
&[
"WCAGAAA",
rule.principle.as_str(),
rule.guideline.as_str(),
&rule.rule_id.into_str(),
]
.join("."),
rule.issue_type.as_str(),
validation.elements,
);

match s.send(issue) {
_ => (),
}
}
}

#[cfg(not(feature = "rayon"))]
/// validate rule and push issue
pub fn push_issue(
validation: Validation,
rule: &Rule,
context: &str,
lang: &str,
issues: &mut Vec<Issue>,
) {
push_issue_base(validation, rule, context, lang, issues)
}

#[cfg(feature = "rayon")]
/// validate rule and push issue parallel
pub fn evaluate_rules_in_parallel(
rules: &[crate::engine::rules::rule::Rule],
node: &(
&&str,
&Vec<(crate::ElementRef<'_>, std::option::Option<taffy::NodeId>)>,
),
auditor: &crate::Auditor,
s: &crossbeam_channel::Sender<Issue>,
) {
use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
rules.par_iter().for_each(|rule| {
match (rule.validate)(&node.1, &auditor) {
crate::engine::rules::rule::RuleValidation::Single(validation) => {
push_issue(validation, rule, &node.0, &auditor.locale, s)
}
crate::engine::rules::rule::RuleValidation::Multi(validations) => {
validations.into_par_iter().for_each(|validation| {
push_issue(validation, rule, &node.0, &auditor.locale, s)
});
}
};
});
}
2 changes: 2 additions & 0 deletions accessibility-rs/src/engine/audit/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/// Audit utils
pub mod audit_utils;
/// the auditor
pub mod auditor;
/// the node tree
Expand Down
76 changes: 41 additions & 35 deletions accessibility-rs/src/engine/audit/wcag.rs
Original file line number Diff line number Diff line change
@@ -1,55 +1,49 @@
use crate::engine::issue::Issue;
use crate::engine::rules::rule::{Rule, RuleValidation, Validation};
use crate::engine::rules::rule::RuleValidation;
use crate::engine::rules::wcag_rule_map::RULES_A;
use crate::i18n::locales::get_message_i18n;
use crate::Auditor;
#[cfg(feature = "rayon")]
use rayon::prelude::*;

/// validate rule and push issue
#[inline]
fn push_issue(
validation: Validation,
rule: &Rule,
context: &str,
lang: &str,
issues: &mut Vec<Issue>,
) {
if !validation.valid {
issues.push(Issue::new(
if !validation.message.is_empty() {
validation.message.into()
} else {
get_message_i18n(&rule, &validation.id, &lang)
},
&context,
&[
"WCAGAAA",
rule.principle.as_str(),
rule.guideline.as_str(),
&rule.rule_id.into_str(),
]
.join("."),
rule.issue_type.as_str(),
validation.elements,
));
}
}

/// baseline for all rules
#[derive(Default)]
/// baseline for all rules
pub struct WCAGAAA;

/// wcag rules to test for
impl WCAGAAA {
/// audit html against WCAGAAA standards
#[cfg(feature = "rayon")]
pub fn audit(auditor: (Auditor<'_>, Option<taffy::TaffyTree>)) -> Vec<Issue> {
use crate::engine::audit::audit_utils::evaluate_rules_in_parallel;

if auditor.0.document.tree.nodes().len() <= 5500 {
WCAGAAA::audit_sync(auditor)
} else {
let (s, r) = crossbeam_channel::unbounded();

auditor.0.tree.par_iter().for_each(|node| {
if let Some(rules) = RULES_A.get(&*node.0) {
evaluate_rules_in_parallel(rules, &node, &auditor.0, &s);
}
});

drop(s);

r.iter().collect()
}
}

/// audit html against WCAGAAA standards
pub fn audit_sync(auditor: (Auditor<'_>, Option<taffy::TaffyTree>)) -> Vec<Issue> {
use crate::engine::audit::audit_utils::push_issue_base;
let mut issues: Vec<Issue> = Vec::new();

for node in auditor.0.tree.iter() {
match RULES_A.get(&*node.0) {
Some(rules) => {
for rule in rules {
match (rule.validate)(&node.1, &auditor.0) {
RuleValidation::Single(validation) => push_issue(
RuleValidation::Single(validation) => push_issue_base(
validation,
rule,
&node.0,
Expand All @@ -58,7 +52,13 @@ impl WCAGAAA {
),
RuleValidation::Multi(validation) => {
for v in validation {
push_issue(v, rule, &node.0, &auditor.0.locale, &mut issues)
push_issue_base(
v,
rule,
&node.0,
&auditor.0.locale,
&mut issues,
)
}
}
};
Expand All @@ -70,4 +70,10 @@ impl WCAGAAA {

issues
}

/// audit html against WCAGAAA standards
#[cfg(not(feature = "rayon"))]
pub fn audit(auditor: (Auditor<'_>, Option<taffy::TaffyTree>)) -> Vec<Issue> {
WCAGAAA::audit_sync(auditor)
}
}
11 changes: 11 additions & 0 deletions accessibility-rs/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,14 @@ fn _audit_large_with_layout() {
));
println!("{:?}", report)
}

#[test]
fn _audit_xlarge() {
let report = accessibility_rs::audit(AuditConfig::new(
mock::MOCK_WEBSITE_XLARGE_HTML,
&mock::MOCK_CSS_RULES_XLARGE,
false,
"en",
));
println!("{:?}", report)
}
5 changes: 5 additions & 0 deletions accessibility-rs/tests/mocks/mock.rs

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions benches/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ pub fn bench_speed(c: &mut Criterion) {
b.iter(|| black_box(audit(AuditConfig::basic(mock::MOCK_WEBSITE_HTML))))
});

group.bench_function(format!("audit: {}", "large-xlarge html"), |b| {
b.iter(|| black_box(audit(AuditConfig::basic(mock::MOCK_WEBSITE_LARGE_HTML))))
});

group.finish();
}

Expand Down
2 changes: 2 additions & 0 deletions benches/mock.rs

Large diffs are not rendered by default.

0 comments on commit 5eb5c9b

Please sign in to comment.