116 / 118 lines covered (98.3%)
1unit module BDD::Behave::Configuration;23# The execution models `--parallel-mode` accepts. `isolated` is the default: it4# gives every spec file its own process, so top-level symbols one file declares5# cannot collide with another file's, and a crashed worker takes one file down6# rather than the run. `lpt` and `queue` share a fixed pool of processes across7# files, trading that isolation for fewer process startups.8our constant PARALLEL-MODES = <isolated lpt queue>;9our constant DEFAULT-PARALLEL-MODE = 'isolated';1011our sub resolve-parallel-mode($requested --> Str) {12 ($requested // DEFAULT-PARALLEL-MODE).lc;13}1415our sub is-parallel-mode(Str $mode --> Bool) {16 so $mode ∈ PARALLEL-MODES;17}1819our class ConfigInclude {20 has Mu $.class is required;21 has Str $.as;22 has Str $.tag;23 has %.meta;2425 method key(--> Str) {26 $!as.defined && $!as.chars ?? $!as !! $!class.^name.split('::')[*-1];27 }28}2930our class ConfigHook {31 has Str $.phase is required;32 has Callable $.block is required;33 has Str $.tag;34 has Str $.exclude-tag;35 has %.meta;3637 method matches-example($example --> Bool) {38 return True unless $!tag.defined39 || $!exclude-tag.defined40 || %!meta.elems;41 my @tags = $example.effective-tags.list;42 if $!tag.defined {43 return False unless @tags.first(* eq $!tag).defined;44 }45 if $!exclude-tag.defined {46 return False if @tags.first(* eq $!exclude-tag).defined;47 }48 for %!meta.kv -> $k, $v {49 my $actual = $example.effective-metadata-value($k);50 return False unless $actual.defined;51 if $v ~~ Bool {52 return False unless ?$actual == $v;53 } else {54 return False unless $actual eq $v;55 }56 }57 True;58 }59}6061our class Configuration {62 has Str $.format is rw;63 has Str $.order is rw;64 has Int $.seed is rw;65 has Str $.seed-mode is rw;66 has Bool $.show-seed is rw;67 has Str $.parallel-mode is rw;68 has Int $.parallel is rw;69 has Int $.parallel-retry is rw;70 has Bool $.progress-total is rw;71 has Int $.fail-fast is rw;72 has Int $.retry is rw;73 has Bool $.only-failures is rw;74 has IO::Path $.failures-path is rw;75 has Bool $.verbose is rw;76 has $.aggregate-failures is rw;77 has Int $.profile-limit is rw;78 has Real $.slow-threshold is rw;79 has Bool $.benchmark-mode is rw;80 has Int $.benchmark-iterations is rw;81 has IO::Path $.benchmark-baseline is rw;82 has IO::Path $.benchmark-save is rw;83 has Real $.benchmark-threshold is rw;84 has Str $.benchmark-format is rw;85 has IO::Path $.benchmark-output is rw;86 has Bool $.coverage is rw;87 has Real $.coverage-minimum is rw;88 has Str $.coverage-format is rw;89 has IO::Path $.coverage-output is rw;90 has IO::Path $.coverage-baseline is rw;91 has Bool $.coverage-branch is rw;92 has Bool $.coverage-counts is rw;9394 has Str @.include-tags;95 has Str @.exclude-tags;96 has Str @.example-patterns;97 has Str @.only-locations;98 has Str @.spec-paths;99 has Str @.coverage-include;100 has Str @.coverage-exclude;101102 has ConfigInclude @.includes;103 has ConfigHook @.hooks;104 has %.metadata-filters;105 has %.metadata-exclude-filters;106 has @.match-filters;107108 method include-tag(*@tags) {109 @!include-tags.append: @tags.map(*.Str);110 self;111 }112113 method exclude-tag(*@tags) {114 @!exclude-tags.append: @tags.map(*.Str);115 self;116 }117118 method example-pattern(*@patterns) {119 @!example-patterns.append: @patterns.map(*.Str);120 self;121 }122123 method only-location(*@locations) {124 @!only-locations.append: @locations.map(*.Str);125 self;126 }127128 method include-spec(*@paths) {129 @!spec-paths.append: @paths.map(*.Str);130 self;131 }132133 method coverage-include-path(*@paths) {134 @!coverage-include.append: @paths.map(*.Str);135 self;136 }137138 method coverage-exclude-path(*@paths) {139 @!coverage-exclude.append: @paths.map(*.Str);140 self;141 }142143 method include(Mu $class, Str :$as, Str :$tag, *%meta) {144 @!includes.push: ConfigInclude.new(:$class, :$as, :$tag, :%meta);145 self;146 }147148 method before-all(&block, Str :$tag, Str :$exclude-tag, *%meta) {149 @!hooks.push: ConfigHook.new(150 :phase<before-all>, :&block, :$tag, :$exclude-tag, :%meta,151 );152 self;153 }154155 method after-all(&block, Str :$tag, Str :$exclude-tag, *%meta) {156 @!hooks.push: ConfigHook.new(157 :phase<after-all>, :&block, :$tag, :$exclude-tag, :%meta,158 );159 self;160 }161162 method before-each(&block, Str :$tag, Str :$exclude-tag, *%meta) {163 @!hooks.push: ConfigHook.new(164 :phase<before-each>, :&block, :$tag, :$exclude-tag, :%meta,165 );166 self;167 }168169 method after-each(&block, Str :$tag, Str :$exclude-tag, *%meta) {170 @!hooks.push: ConfigHook.new(171 :phase<after-each>, :&block, :$tag, :$exclude-tag, :%meta,172 );173 self;174 }175176 method around-each(&block, Str :$tag, Str :$exclude-tag, *%meta) {177 @!hooks.push: ConfigHook.new(178 :phase<around-each>, :&block, :$tag, :$exclude-tag, :%meta,179 );180 self;181 }182183 method hooks-for(Str $phase --> List) {184 @!hooks.grep(*.phase eq $phase).List;185 }186187 method filter(*%pairs) {188 for %pairs.kv -> $k, $v { %!metadata-filters{$k} = $v }189 self;190 }191192 method exclude-filter(*%pairs) {193 for %pairs.kv -> $k, $v { %!metadata-exclude-filters{$k} = $v }194 self;195 }196197 method filter-run-when-matching(*@keys, *%pairs) {198 for @keys -> $k { @!match-filters.push: $k.Str => True }199 for %pairs.kv -> $k, $v { @!match-filters.push: $k.Str => $v }200 self;201 }202203 method merge(Configuration $other --> Configuration) {204 my $result = Configuration.new;205 for <format order seed seed-mode show-seed parallel-mode parallel parallel-retry progress-total fail-fast retry only-failures failures-path verbose aggregate-failures206 profile-limit slow-threshold207 benchmark-mode benchmark-iterations benchmark-baseline benchmark-save208 benchmark-threshold benchmark-format benchmark-output209 coverage coverage-minimum coverage-format coverage-output210 coverage-baseline coverage-branch coverage-counts> -> $attr {211 my $self-val = self."$attr"();212 my $other-val = $other."$attr"();213 my $picked = $other-val.defined ?? $other-val !! $self-val;214 $result."$attr"() = $picked if $picked.defined;215 }216217 $result.include-tags.append: |self.include-tags, |$other.include-tags;218 $result.exclude-tags.append: |self.exclude-tags, |$other.exclude-tags;219 $result.example-patterns.append: |self.example-patterns, |$other.example-patterns;220 $result.only-locations.append: |self.only-locations, |$other.only-locations;221 $result.spec-paths.append: |self.spec-paths, |$other.spec-paths;222 $result.coverage-include.append: |self.coverage-include, |$other.coverage-include;223 $result.coverage-exclude.append: |self.coverage-exclude, |$other.coverage-exclude;224 $result.includes.append: |self.includes, |$other.includes;225 $result.hooks.append: |self.hooks, |$other.hooks;226 $result.match-filters.append: |self.match-filters, |$other.match-filters;227228 for self.metadata-filters.kv -> $k, $v {229 $result.metadata-filters{$k} = $v;230 }231 for $other.metadata-filters.kv -> $k, $v {232 $result.metadata-filters{$k} = $v;233 }234 for self.metadata-exclude-filters.kv -> $k, $v {235 $result.metadata-exclude-filters{$k} = $v;236 }237 for $other.metadata-exclude-filters.kv -> $k, $v {238 $result.metadata-exclude-filters{$k} = $v;239 }240241 $result;242 }243244}245246our sub defaults(--> Configuration) {247 my $c = Configuration.new;248 $c.format = 'progress';249 $c.order = 'random';250 $c.seed-mode = 'xor';251 $c.show-seed = False;252 $c.parallel-mode = DEFAULT-PARALLEL-MODE;253 $c.fail-fast = 0;254 $c.retry = 0;255 $c.only-failures = False;256 $c.verbose = False;257 $c.aggregate-failures = False;258 $c.profile-limit = 0;259 $c.slow-threshold = 0.Real;260 $c.benchmark-mode = False;261 $c.benchmark-iterations = 1;262 $c.benchmark-threshold = 0.10;263 $c.benchmark-format = 'text';264 $c.coverage = False;265 $c.coverage-minimum = 0.Real;266 $c.coverage-format = 'html';267 $c.coverage-branch = False;268 $c.coverage-counts = False;269 $c;270}271272our sub user-config-path(--> IO::Path) {273 my $home = %*ENV<HOME> // '';274 return IO::Path unless $home.chars;275 $home.IO.add('.behave');276}277278our sub project-config-path(IO::Path :$base = $*CWD --> IO::Path) {279 $base.IO.add('.behave');280}281282our sub load-file(IO::Path $path --> Configuration) {283 my $config = Configuration.new;284 return $config unless $path.defined && $path.e;285 use MONKEY-SEE-NO-EVAL;286 {287 my $*BEHAVE-CONFIG = $config;288 try {289 EVALFILE $path;290 CATCH {291 default {292 die "Failed to load config file '{$path}': {.message}";293 }294 }295 }296 }297 $config;298}299300our sub configure-behave(&block --> Nil) is export {301 my $config = $*BEHAVE-CONFIG;302 die "configure-behave called outside of a .behave config file"303 unless $config.defined;304 block($config);305}