use std::cell::Cell;
use crate::test_support::check_func_space_only;
use crate::*;
#[cfg(feature = "perl")]
type Decisions = [u64; 3];
#[cfg(feature = "perl")]
type Halstead = [u64; 4];
#[cfg(feature = "perl")]
fn measure(body: &str) -> (Decisions, Halstead) {
let ([cyclomatic, _, conditions, cognitive], halstead) = measure_with_branches(body);
([cyclomatic, conditions, cognitive], halstead)
}
#[cfg(feature = "perl")]
fn measure_with_branches(body: &str) -> ([u64; 4], Halstead) {
let out = Cell::new(([0; 4], [0; 4]));
check_func_space_only::<PerlParser, _>(
&format!("sub f {{ {body} }}\n"),
"foo.pl",
&[
Metric::Cyclomatic,
Metric::Abc,
Metric::Cognitive,
Metric::Halstead,
],
|space| {
let m = &space.metrics;
out.set((
[
m.cyclomatic.cyclomatic_sum(),
m.abc.branches_sum(),
m.abc.conditions_sum(),
m.cognitive.cognitive_sum(),
],
[
m.halstead.unique_operators(),
m.halstead.total_operators(),
m.halstead.unique_operands(),
m.halstead.total_operands(),
],
));
},
);
out.get()
}
#[cfg(feature = "perl")]
#[test]
fn perl_and_key_scores_like_an_or_key() {
let rows: [(&str, &str, Decisions); 12] = [
("my %h; return $h{or};", "my %h; return $h{and};", [2, 0, 0]),
(
"my %h; return $h{ or };",
"my %h; return $h{ and };",
[2, 0, 0],
),
(
"my $r; return $r->{or};",
"my $r; return $r->{and};",
[2, 0, 0],
),
(
"my %h; if ($h{or}) { return 1; } return 0;",
"my %h; if ($h{and}) { return 1; } return 0;",
[3, 1, 1],
),
(
"my %h = (or => 1); return 1;",
"my %h = (and => 1); return 1;",
[2, 0, 0],
),
(
"my $h = { or => 1 }; return $h;",
"my $h = { and => 1 }; return $h;",
[2, 0, 0],
),
("f(x => 1, or => 2);", "f(x => 1, and => 2);", [2, 0, 0]),
("f(or => !$a);", "f(and => !$a);", [2, 1, 0]),
(
"my $y = $c ? 1 : (or => !$a);",
"my $y = $c ? 1 : (and => !$a);",
[3, 3, 1],
),
(
"if (or => $x > 1) { return 1; } return 0;",
"if (and => $x > 1) { return 1; } return 0;",
[3, 1, 1],
),
(
"my @a = (1 - or => 2);",
"my @a = (1 - and => 2);",
[2, 0, 0],
),
(
"my @a = ($x - or => 2, or => 3);",
"my @a = ($x - and => 2, and => 3);",
[2, 0, 0],
),
];
for (twin, key, decisions) in rows {
assert_eq!(
key.replace("and", "or"),
twin,
"`{key}` drifted from its twin"
);
let want = measure(twin);
assert_eq!(want.0, decisions, "`{twin}` moved; re-derive the row");
assert_eq!(measure(key), want, "`{key}` must score like `{twin}`");
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_and_key_in_a_condition_slot_pays_the_slot() {
let twin = "my %h; if (%h = (or => 1)) { return 1; } return 0;";
let key = "my %h; if (%h = (and => 1)) { return 1; } return 0;";
let want = measure(twin);
assert_eq!(want.0, [3, 1, 1], "`{twin}` moved; re-derive the row");
assert_eq!(measure(key), want, "`{key}` must score like `{twin}`");
}
#[cfg(feature = "perl")]
#[test]
fn perl_and_operator_still_scores_like_ampamp() {
let rows: [(&str, &str, Decisions); 5] = [
("return ($a && $b);", "return ($a and $b);", [3, 2, 1]),
(
"return ($a && $b && $c);",
"return ($a and $b and $c);",
[4, 3, 1],
),
(
"if (!$a && $b) { return 1; } return 0;",
"if (not $a and $b) { return 1; } return 0;",
[4, 2, 2],
),
(
"return ($a > $b && $c);",
"return ($a > $b and $c);",
[3, 2, 1],
),
(
"return (($a > $b) && ($c ? 1 : 2));",
"return ($a > $b and $c ? 1 : 2);",
[4, 3, 2],
),
];
for (twin, operator, decisions) in rows {
assert!(operator.contains(" and "), "`{operator}` lost its `and`");
let want = measure(twin).0;
assert_eq!(want, decisions, "`{twin}` moved; re-derive the row");
assert_eq!(
measure(operator).0,
want,
"`{operator}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_not_key_scores_like_an_or_key() {
let rows: [(&str, &str, Decisions, Halstead); 13] = [
(
"my %h; return $h{or};",
"my %h; return $h{not};",
[2, 0, 0],
[6, 8, 4, 4],
),
(
"my %h; return $h{ or };",
"my %h; return $h{ not };",
[2, 0, 0],
[6, 8, 4, 4],
),
(
"my $r; return $r->{or};",
"my $r; return $r->{not};",
[2, 0, 0],
[7, 10, 3, 4],
),
(
"my %h = (or => 1); return 1;",
"my %h = (not => 1); return 1;",
[2, 0, 0],
[8, 9, 4, 5],
),
(
"my $r = { or => 1 }; return 1;",
"my $r = { not => 1 }; return 1;",
[2, 0, 0],
[8, 10, 4, 5],
),
(
"f(x => 1, or => 2);",
"f(x => 1, not => 2);",
[2, 0, 0],
[6, 7, 5, 6],
),
("f(or => 1);", "f(not => 1);", [2, 0, 0], [5, 5, 3, 4]),
(
"return (or => 1);",
"return (not => 1);",
[2, 0, 0],
[6, 6, 3, 3],
),
("f(or => !$a);", "f(not => !$a);", [2, 1, 0], [7, 7, 3, 4]),
(
"my $y = $c ? 1 : (or => 2);",
"my $y = $c ? 1 : (not => 2);",
[3, 2, 1],
[10, 11, 6, 6],
),
(
"my @a = ($x - or => 2);",
"my @a = ($x - not => 2);",
[2, 0, 0],
[9, 9, 5, 5],
),
(
"my %h = (\n # -----\n or => 1,\n or => 2,\n);",
"my %h = (\n # -----\n not => 1,\n not => 2,\n);",
[2, 0, 0],
[8, 10, 5, 6],
),
(
"f(and => 1, or => 2);",
"f(and => 1, not => 2);",
[2, 0, 0],
[6, 7, 5, 6],
),
];
for (twin, key, decisions, halstead) in rows {
assert_eq!(
key.replace("not", "or"),
twin,
"`{key}` drifted from its twin"
);
let want = measure(twin);
assert_eq!(
want,
(decisions, halstead),
"`{twin}` moved; re-derive the row"
);
assert_eq!(measure(key), want, "`{key}` must score like `{twin}`");
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_not_operator_still_scores_like_bang() {
let rows: [(&str, &str, Decisions, Halstead); 6] = [
("!$a;", "not $a;", [2, 0, 0], [5, 5, 2, 2]),
(
"if (!$a) { return 1; } return 0;",
"if (not $a) { return 1; } return 0;",
[3, 1, 1],
[8, 11, 4, 4],
),
("!($a);", "not($a);", [2, 0, 0], [6, 6, 2, 2]),
("f(!$a);", "f(not $a);", [2, 1, 0], [6, 6, 2, 3]),
(
"return (!$a && $b);",
"return (not $a and $b);",
[3, 2, 1],
[8, 9, 3, 3],
),
(
"my %h = (or => !$a); return 1;",
"my %h = (not => not $a); return 1;",
[2, 0, 0],
[10, 11, 5, 5],
),
];
for (twin, operator, decisions, halstead) in rows {
assert!(operator.contains("not"), "`{operator}` lost its `not`");
let want = measure(twin);
assert_eq!(
want,
(decisions, halstead),
"`{twin}` moved; re-derive the row"
);
assert_eq!(
measure(operator),
want,
"`{operator}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
const DASH_KEY_ROWS: [(&str, &str, [u64; 4], Halstead); 20] = [
(
"my %h; return $h{'-foo'};",
"my %h; return $h{-foo};",
[2, 0, 0, 0],
[6, 8, 4, 4],
),
(
"my %h; return $h{'-foo' # why\n};",
"my %h; return $h{-foo # why\n};",
[2, 0, 0, 0],
[6, 8, 4, 4],
),
(
"my $r; return $r->{'-foo'};",
"my $r; return $r->{-foo};",
[2, 0, 0, 0],
[7, 10, 3, 4],
),
(
"my %h; return @h{'-foo'};",
"my %h; return @h{-foo};",
[2, 0, 0, 0],
[5, 7, 4, 4],
),
(
"my %h; if ($h{'-foo'}) { return 1; } return 0;",
"my %h; if ($h{-foo}) { return 1; } return 0;",
[3, 0, 1, 1],
[8, 13, 6, 6],
),
(
"my %h = ('-foo' => 1); return 1;",
"my %h = (-foo => 1); return 1;",
[2, 0, 0, 0],
[8, 9, 4, 5],
),
(
"my $r = { '-foo' => 1 }; return $r;",
"my $r = { -foo => 1 }; return $r;",
[2, 0, 0, 0],
[8, 11, 4, 5],
),
(
"f('-text' => 1);",
"f(-text => 1);",
[2, 1, 0, 0],
[5, 5, 3, 4],
),
(
"f('-text' => 1, ext => 2);",
"f(-text => 1, ext => 2);",
[2, 2, 0, 0],
[6, 7, 5, 6],
),
(
"my %h = ('-x' => 1); return 1;",
"my %h = (-x => 1); return 1;",
[2, 0, 0, 0],
[8, 9, 4, 5],
),
(
"my %h = ('-x' => 1); my %g = ('-x' => 2);",
"my %h = (-x => 1); my %g = (-x => 2);",
[2, 0, 0, 0],
[7, 12, 6, 7],
),
(
"f('-x' => foo);",
"f(-x => foo);",
[2, 2, 0, 0],
[5, 5, 3, 4],
),
(
"my %h; return $h{'-not'};",
"my %h; return $h{-not};",
[2, 0, 0, 0],
[6, 8, 4, 4],
),
(
"my $r; return $r->{'-and'};",
"my $r; return $r->{-and};",
[2, 0, 0, 0],
[7, 10, 3, 4],
),
(
"my %h = ('-and' => 1); return 1;",
"my %h = (-and => 1); return 1;",
[2, 0, 0, 0],
[8, 9, 4, 5],
),
(
"my $r = { '-not' => 1 }; return $r;",
"my $r = { -not => 1 }; return $r;",
[2, 0, 0, 0],
[8, 11, 4, 5],
),
(
"f('-not' => 1, not => 2);",
"f(-not => 1, not => 2);",
[2, 1, 0, 0],
[6, 7, 5, 6],
),
(
"f('-and' => 1, and => 2);",
"f(-and => 1, and => 2);",
[2, 1, 0, 0],
[6, 7, 5, 6],
),
(
"f('-and' => !$a);",
"f(-and => !$a);",
[2, 1, 1, 0],
[7, 7, 3, 4],
),
(
"f('-not' => !$a);",
"f(-not => !$a);",
[2, 1, 1, 0],
[7, 7, 3, 4],
),
];
#[cfg(feature = "perl")]
#[test]
fn perl_dash_key_scores_like_its_quoted_twin() {
for (twin, key, decisions, halstead) in DASH_KEY_ROWS {
assert_eq!(twin.replace('\'', ""), key, "`{key}` drifted from its twin");
let want = measure_with_branches(twin);
assert_eq!(
want,
(decisions, halstead),
"`{twin}` moved; re-derive the row"
);
assert_eq!(
measure_with_branches(key),
want,
"`{key}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_spaced_dash_key_keeps_its_sign() {
for (twin, key) in [
("f('-not' => 1, not => 2);", "f(- not => 1, not => 2);"),
("f('-and' => 1, and => 2);", "f(- and => 1, and => 2);"),
(
"f('-not' => 1, not => 2);",
"f(- # c\n not => 1, not => 2);",
),
] {
let want = measure_with_branches(twin);
assert_eq!(
want,
([2, 1, 0, 0], [6, 7, 5, 6]),
"`{twin}` moved; re-derive the row"
);
assert_eq!(
measure_with_branches(key),
want,
"`{key}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_file_test_key_stops_at_a_line_break() {
let comma = ([2, 0, 0, 0], [8, 9, 3, 4]);
let quoted = ([2, 0, 0, 0], [8, 9, 4, 5]);
let word = ([2, 1, 0, 0], [5, 5, 3, 4]);
for (twin, want, key) in [
(
"my @p = (-f, 7); return @p;",
comma,
"my @p = (-f # c\n=> 7); return @p;",
),
(
"my @p = (-f, 7); return @p;",
comma,
"my @p = (-f\n=> 7); return @p;",
),
(
"my @p = ('-f' => 7); return @p;",
quoted,
"my @p = (-f \t => 7); return @p;",
),
("f('-foo' => 1);", word, "f(-foo # c\n=> 1);"),
("f('-foo' => 1);", word, "f(-foo\n=> 1);"),
] {
assert_eq!(
measure_with_branches(twin),
want,
"`{twin}` moved; re-derive the row"
);
assert_eq!(
measure_with_branches(key),
want,
"`{key}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_file_test_outside_a_key_keeps_its_reading() {
let rows: [(&str, [u64; 4], Halstead); 11] = [
("my $z = -f $x;", [2, 0, 0, 0], [6, 7, 3, 3]),
(
"if (-e $x) { return 1; } return 0;",
[3, 0, 1, 1],
[7, 10, 4, 4],
),
("my $z = -d _;", [2, 1, 0, 0], [6, 6, 3, 3]),
("my $z = -f -w $x;", [2, 0, 0, 0], [6, 7, 3, 3]),
("my %h; return $h{-f $x};", [2, 0, 0, 0], [6, 9, 4, 4]),
("my %h; return $h{-f foo};", [2, 1, 0, 0], [6, 8, 4, 4]),
("return -foo;", [2, 1, 0, 0], [4, 4, 2, 2]),
("my %h; return $h{$a . -foo};", [2, 1, 0, 0], [7, 10, 5, 5]),
("f(-foo, 1);", [2, 2, 0, 0], [5, 5, 3, 4]),
("return -not $x;", [2, 0, 0, 0], [7, 7, 2, 2]),
("return $a - not $x;", [2, 0, 0, 0], [7, 8, 3, 3]),
];
for (body, decisions, halstead) in rows {
assert_eq!(
measure_with_branches(body),
(decisions, halstead),
"`{body}` changed its reading"
);
}
}