Bläddra i källkod

Write every pattern under use re '/aasx', and enable the regex policies

The strict profile wants /aa, /s and /x on every pattern:
RegularExpressions::RequireDefault, RequireDotMatchAnything and
RequireExtendedFormatting.  Every file now says use re '/aasx', which
those policies accept.  The modules and scripts already said
use re '/aa', and the tests said nothing.

Each of those flags can change what a pattern matches, so the patterns
were rewritten to match what they matched before.  Outside a character
class and a \Q...\E, a literal space is now [ ], a # is [#], and a .
in a pattern without /s is \N.  In lib/ this was seven patterns.  One
of them was s/ /%20/g in DataModule::_fixup, which under /x would have
been an empty pattern.  Three tests asked for /a, which overrides /aa,
and now ask for nothing.

To check the rewrite, I compiled each of the 359 patterns that have no
interpolation twice under use re 'debug': the old text with its old
flags plus /aa, and the new text under /aasx.  All 359 compile to the
same program, apart from node offsets.  The same check reports a
difference for "a.b" under /s and "no recipe" under /x.  The other 27
patterns interpolate a \Q...\E, a number or a single word, which these
flags do not change.  /aa is new to the tests, and their data is ASCII.

The suite fails when _fixup goes back to s/ /%20/g, and when the log
patterns in Trog::Log::DBI go back to their literal spaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
troglodyne-bot 1 vecka sedan
förälder
incheckning
64ce9af8e7
83 ändrade filer med 228 tillägg och 204 borttagningar
  1. 4 4
      .perlcriticrc
  2. 1 0
      Makefile.PL
  3. 1 1
      bin/build_index.pl
  4. 1 1
      bin/favicon_mongler.pl
  5. 1 1
      bin/migrate.pl
  6. 1 1
      bin/tcms-hostname
  7. 1 1
      bin/tcms-useradd
  8. 1 1
      bin/tcms-vault-key
  9. 1 1
      bin/tcmslogin
  10. 1 1
      bin/totp
  11. 1 1
      lib/TCMS.pm
  12. 1 1
      lib/Theme.pm
  13. 1 1
      lib/Trog/Auth.pm
  14. 1 1
      lib/Trog/Component.pm
  15. 1 1
      lib/Trog/Component/CategoryBar.pm
  16. 1 1
      lib/Trog/Component/EmojiPicker.pm
  17. 1 1
      lib/Trog/Component/FootBar.pm
  18. 1 1
      lib/Trog/Component/Footer.pm
  19. 1 1
      lib/Trog/Component/Header.pm
  20. 1 1
      lib/Trog/Component/HtmlTitle.pm
  21. 1 1
      lib/Trog/Component/LeftBar.pm
  22. 1 1
      lib/Trog/Component/MidTitle.pm
  23. 1 1
      lib/Trog/Component/PostFooter.pm
  24. 1 1
      lib/Trog/Component/PostHeader.pm
  25. 1 1
      lib/Trog/Component/RightBar.pm
  26. 1 1
      lib/Trog/Component/TopBar.pm
  27. 4 4
      lib/Trog/Config.pm
  28. 1 1
      lib/Trog/Data.pm
  29. 1 1
      lib/Trog/Data/DUMMY.pm
  30. 1 1
      lib/Trog/Data/FlatFile.pm
  31. 1 1
      lib/Trog/Data/SQLite.pm
  32. 2 2
      lib/Trog/DataModule.pm
  33. 1 1
      lib/Trog/DataSource.pm
  34. 1 1
      lib/Trog/DataSource/DirIndex.pm
  35. 2 2
      lib/Trog/DataSource/ProvisionedVirt.pm
  36. 1 1
      lib/Trog/DataSource/Virt.pm
  37. 1 1
      lib/Trog/Email.pm
  38. 1 1
      lib/Trog/Enrich/Invoice.pm
  39. 1 1
      lib/Trog/FileHandler.pm
  40. 1 1
      lib/Trog/Log.pm
  41. 3 3
      lib/Trog/Log/DBI.pm
  42. 1 1
      lib/Trog/Log/Metrics.pm
  43. 1 1
      lib/Trog/Log/Syslog.pm
  44. 1 1
      lib/Trog/Renderer.pm
  45. 1 1
      lib/Trog/Renderer/Base.pm
  46. 1 1
      lib/Trog/Renderer/blob.pm
  47. 1 1
      lib/Trog/Renderer/css.pm
  48. 1 1
      lib/Trog/Renderer/email.pm
  49. 1 1
      lib/Trog/Renderer/html.pm
  50. 1 1
      lib/Trog/Renderer/javascript.pm
  51. 1 1
      lib/Trog/Renderer/json.pm
  52. 1 1
      lib/Trog/Renderer/text.pm
  53. 1 1
      lib/Trog/Routes/HTML.pm
  54. 1 1
      lib/Trog/Routes/JSON.pm
  55. 1 1
      lib/Trog/Routes/TXT.pm
  56. 1 1
      lib/Trog/SQLite/TagIndex.pm
  57. 1 1
      lib/Trog/Themes.pm
  58. 1 1
      lib/Trog/Utils.pm
  59. 1 1
      lib/Trog/Vars.pm
  60. 1 1
      lib/Trog/Vault.pm
  61. 1 0
      t/Trog-Auth-libravatar.t
  62. 1 0
      t/Trog-Auth-totp-mandatory.t
  63. 8 7
      t/Trog-Component-page.t
  64. 8 7
      t/Trog-Component.t
  65. 1 0
      t/Trog-Config.t
  66. 4 3
      t/Trog-Data-FlatFile-read.t
  67. 5 4
      t/Trog-Data-SQLite.t
  68. 2 1
      t/Trog-Data.t
  69. 7 6
      t/Trog-DataModule-schema.t
  70. 1 0
      t/Trog-DataSource-DirIndex.t
  71. 14 13
      t/Trog-DataSource-ProvisionedVirt.t
  72. 9 8
      t/Trog-DataSource-Virt.t
  73. 5 4
      t/Trog-DataSource.t
  74. 2 1
      t/Trog-Log-DBI.t
  75. 8 7
      t/Trog-Log-Syslog.t
  76. 1 0
      t/Trog-Routes-HTML-config.t
  77. 1 0
      t/Trog-Routes-HTML-post-wizard-browser.t
  78. 1 0
      t/Trog-Routes-HTML-series-lifecycle-sqlite.t
  79. 60 59
      t/Trog-Routes-HTML-series-lifecycle.t
  80. 2 1
      t/Trog-SQLite-TagIndex.t
  81. 1 0
      t/Trog-Themes-themed-templates.t
  82. 7 6
      t/Trog-Vault.t
  83. 9 8
      t/bin-migrate.t

+ 4 - 4
.perlcriticrc

@@ -44,8 +44,8 @@ allow-unsafe = 1
 # thing it tends to catch is a contributor's name in the section Dist::Zilla
 # thing it tends to catch is a contributor's name in the section Dist::Zilla
 # generates, and spelling somebody's name wrong is not a code standard.
 # generates, and spelling somebody's name wrong is not a code standard.
 #[CodeLayout::RequireASCII]
 #[CodeLayout::RequireASCII]
-#[RegularExpressions::RequireDefault]
-#strict = 1
+[RegularExpressions::RequireDefault]
+strict = 1
 #[RegularExpressions::PreventUselessMetacharacterEscapes]
 #[RegularExpressions::PreventUselessMetacharacterEscapes]
 # PEBCAK catches
 # PEBCAK catches
 [BuiltinFunctions::ProhibitDeleteOnArrays]
 [BuiltinFunctions::ProhibitDeleteOnArrays]
@@ -224,8 +224,8 @@ exclude_functions = print
 [RegularExpressions::ProhibitSingleCharAlternation]
 [RegularExpressions::ProhibitSingleCharAlternation]
 [RegularExpressions::ProhibitUnusedCapture]
 [RegularExpressions::ProhibitUnusedCapture]
 [RegularExpressions::RequireBracesForMultiline]
 [RegularExpressions::RequireBracesForMultiline]
-#[RegularExpressions::RequireDotMatchAnything]
-#[RegularExpressions::RequireExtendedFormatting]
+[RegularExpressions::RequireDotMatchAnything]
+[RegularExpressions::RequireExtendedFormatting]
 [Subroutines::ProhibitAmpersandSigils]
 [Subroutines::ProhibitAmpersandSigils]
 #[Subroutines::ProhibitBuiltinHomonyms]
 #[Subroutines::ProhibitBuiltinHomonyms]
 [Subroutines::ProhibitManyArgs]
 [Subroutines::ProhibitManyArgs]

+ 1 - 0
Makefile.PL

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use ExtUtils::MakeMaker;
 use ExtUtils::MakeMaker;

+ 1 - 1
bin/build_index.pl

@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 #!/usr/bin/env perl
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use lib 'lib';
 use lib 'lib';
 use Trog::Data;
 use Trog::Data;

+ 1 - 1
bin/favicon_mongler.pl

@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 #!/usr/bin/env perl
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Cwd            ();
 use Cwd            ();
 use File::Basename ();
 use File::Basename ();

+ 1 - 1
bin/migrate.pl

@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 #!/usr/bin/env perl
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use FindBin;
 use FindBin;
 use lib "$FindBin::Bin/../lib";
 use lib "$FindBin::Bin/../lib";

+ 1 - 1
bin/tcms-hostname

@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 #!/usr/bin/env perl
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use FindBin::libs;
 use FindBin::libs;
 
 

+ 1 - 1
bin/tcms-useradd

@@ -3,7 +3,7 @@
 package tcms::useradd;
 package tcms::useradd;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use FindBin::libs;
 use FindBin::libs;
 use Getopt::Long;
 use Getopt::Long;

+ 1 - 1
bin/tcms-vault-key

@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 #!/usr/bin/env perl
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use FindBin::libs;
 use FindBin::libs;
 
 

+ 1 - 1
bin/tcmslogin

@@ -3,7 +3,7 @@
 package tcms::login;
 package tcms::login;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use FindBin::libs;
 use FindBin::libs;
 use Getopt::Long;
 use Getopt::Long;

+ 1 - 1
bin/totp

@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 #!/usr/bin/env perl
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use FindBin::libs;
 use FindBin::libs;
 
 

+ 1 - 1
lib/TCMS.pm

@@ -1,7 +1,7 @@
 package TCMS;
 package TCMS;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Carp         qw{confess};
 use Carp         qw{confess};
 use Clone        qw{clone};
 use Clone        qw{clone};

+ 1 - 1
lib/Theme.pm

@@ -1,7 +1,7 @@
 package Theme;
 package Theme;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 =head1 Theme
 =head1 Theme
 
 

+ 1 - 1
lib/Trog/Auth.pm

@@ -1,7 +1,7 @@
 package Trog::Auth;
 package Trog::Auth;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use FindBin::libs;
 use FindBin::libs;
 
 

+ 1 - 1
lib/Trog/Component.pm

@@ -1,7 +1,7 @@
 package Trog::Component;
 package Trog::Component;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Text::Xslate ();
 use Text::Xslate ();
 
 

+ 1 - 1
lib/Trog/Component/CategoryBar.pm

@@ -1,7 +1,7 @@
 package Trog::Component::CategoryBar;
 package Trog::Component::CategoryBar;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 
 

+ 1 - 1
lib/Trog/Component/EmojiPicker.pm

@@ -1,7 +1,7 @@
 package Trog::Component::EmojiPicker;
 package Trog::Component::EmojiPicker;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use File::Slurper ();
 use File::Slurper ();
 use JSON::MaybeXS;
 use JSON::MaybeXS;

+ 1 - 1
lib/Trog/Component/FootBar.pm

@@ -1,7 +1,7 @@
 package Trog::Component::FootBar;
 package Trog::Component::FootBar;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 
 

+ 1 - 1
lib/Trog/Component/Footer.pm

@@ -1,7 +1,7 @@
 package Trog::Component::Footer;
 package Trog::Component::Footer;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 
 

+ 1 - 1
lib/Trog/Component/Header.pm

@@ -1,7 +1,7 @@
 package Trog::Component::Header;
 package Trog::Component::Header;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 use Trog::Themes;
 use Trog::Themes;

+ 1 - 1
lib/Trog/Component/HtmlTitle.pm

@@ -1,7 +1,7 @@
 package Trog::Component::HtmlTitle;
 package Trog::Component::HtmlTitle;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 
 

+ 1 - 1
lib/Trog/Component/LeftBar.pm

@@ -1,7 +1,7 @@
 package Trog::Component::LeftBar;
 package Trog::Component::LeftBar;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 
 

+ 1 - 1
lib/Trog/Component/MidTitle.pm

@@ -1,7 +1,7 @@
 package Trog::Component::MidTitle;
 package Trog::Component::MidTitle;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 
 

+ 1 - 1
lib/Trog/Component/PostFooter.pm

@@ -1,7 +1,7 @@
 package Trog::Component::PostFooter;
 package Trog::Component::PostFooter;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 use Trog::Themes;
 use Trog::Themes;

+ 1 - 1
lib/Trog/Component/PostHeader.pm

@@ -1,7 +1,7 @@
 package Trog::Component::PostHeader;
 package Trog::Component::PostHeader;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 use Trog::Themes;
 use Trog::Themes;

+ 1 - 1
lib/Trog/Component/RightBar.pm

@@ -1,7 +1,7 @@
 package Trog::Component::RightBar;
 package Trog::Component::RightBar;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 
 

+ 1 - 1
lib/Trog/Component/TopBar.pm

@@ -1,7 +1,7 @@
 package Trog::Component::TopBar;
 package Trog::Component::TopBar;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Component ();
 use Trog::Component ();
 
 

+ 4 - 4
lib/Trog/Config.pm

@@ -1,7 +1,7 @@
 package Trog::Config;
 package Trog::Config;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use FindBin::libs;
 use FindBin::libs;
 
 
@@ -85,19 +85,19 @@ sub schema ( $file = $default ) {
         $line =~ s/\s+$//;
         $line =~ s/\s+$//;
         next unless length $line;
         next unless length $line;
 
 
-        if ( $line =~ m/^\[(.+)\]$/ ) {
+        if ( $line =~ m/^\[(\N+)\]$/ ) {
             $section = { section => $1, fields => [] };
             $section = { section => $1, fields => [] };
             push( @sections, $section );
             push( @sections, $section );
             @comments = ();
             @comments = ();
             next;
             next;
         }
         }
 
 
-        if ( $line =~ m/^[#;]\s?(.*)$/ ) {
+        if ( $line =~ m/^[#;]\s?(\N*)$/ ) {
             push( @comments, $1 );
             push( @comments, $1 );
             next;
             next;
         }
         }
 
 
-        my ( $key, $value ) = $line =~ m/^([^=]+?)\s*=\s*(.*)$/;
+        my ( $key, $value ) = $line =~ m/^([^=]+?)\s*=\s*(\N*)$/;
         next unless defined $key;
         next unless defined $key;
         next unless $section;
         next unless $section;
 
 

+ 1 - 1
lib/Trog/Data.pm

@@ -1,7 +1,7 @@
 package Trog::Data;
 package Trog::Data;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::Log qw{:all};
 use Trog::Log qw{:all};
 
 

+ 1 - 1
lib/Trog/Data/DUMMY.pm

@@ -1,7 +1,7 @@
 package Trog::Data::DUMMY;
 package Trog::Data::DUMMY;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Carp qw{confess};
 use Carp qw{confess};
 use Errno();
 use Errno();

+ 1 - 1
lib/Trog/Data/FlatFile.pm

@@ -1,7 +1,7 @@
 package Trog::Data::FlatFile;
 package Trog::Data::FlatFile;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Carp           qw{confess};
 use Carp           qw{confess};
 use Fcntl          qw{LOCK_EX};
 use Fcntl          qw{LOCK_EX};

+ 1 - 1
lib/Trog/Data/SQLite.pm

@@ -1,7 +1,7 @@
 package Trog::Data::SQLite;
 package Trog::Data::SQLite;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Carp qw{confess};
 use Carp qw{confess};
 use JSON::MaybeXS;
 use JSON::MaybeXS;

+ 2 - 2
lib/Trog/DataModule.pm

@@ -1,7 +1,7 @@
 package Trog::DataModule;
 package Trog::DataModule;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use FindBin::libs;
 use FindBin::libs;
 
 
@@ -142,7 +142,7 @@ sub _fixup ( $self, @filtered ) {
             next unless exists $subj->{$param};
             next unless exists $subj->{$param};
 
 
             #XXX I don't remember what this fixes, but it also breaks things.  URI::Escape usage instead is indicated.
             #XXX I don't remember what this fixes, but it also breaks things.  URI::Escape usage instead is indicated.
-            $subj->{$param} =~ s/ /%20/g;
+            $subj->{$param} =~ s/[ ]/%20/g;
         }
         }
 
 
         $user2display{ $subj->{user} } //= Trog::Auth::username2display( $subj->{user} );
         $user2display{ $subj->{user} } //= Trog::Auth::username2display( $subj->{user} );

+ 1 - 1
lib/Trog/DataSource.pm

@@ -1,7 +1,7 @@
 package Trog::DataSource;
 package Trog::DataSource;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::DataModule ();
 use Trog::DataModule ();
 use Trog::Log        qw{:all};
 use Trog::Log        qw{:all};

+ 1 - 1
lib/Trog/DataSource/DirIndex.pm

@@ -1,7 +1,7 @@
 package Trog::DataSource::DirIndex;
 package Trog::DataSource::DirIndex;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Cwd            ();
 use Cwd            ();
 use Digest::SHA    qw{sha256_hex};
 use Digest::SHA    qw{sha256_hex};

+ 2 - 2
lib/Trog/DataSource/ProvisionedVirt.pm

@@ -1,7 +1,7 @@
 package Trog::DataSource::ProvisionedVirt;
 package Trog::DataSource::ProvisionedVirt;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use parent qw{Trog::DataSource::Virt};
 use parent qw{Trog::DataSource::Virt};
 
 
@@ -205,7 +205,7 @@ sub status ($domain) {
 
 
     my %status;
     my %status;
     foreach my $line ( split( "\n", $raw ) ) {
     foreach my $line ( split( "\n", $raw ) ) {
-        my ( $key, $value ) = $line =~ m/^(\w+)=(.*)$/;
+        my ( $key, $value ) = $line =~ m/^(\w+)=(\N*)$/;
         $status{$key} = $value if defined $key;
         $status{$key} = $value if defined $key;
     }
     }
     return undef unless $status{state};
     return undef unless $status{state};

+ 1 - 1
lib/Trog/DataSource/Virt.pm

@@ -1,7 +1,7 @@
 package Trog::DataSource::Virt;
 package Trog::DataSource::Virt;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use File::Path();
 use File::Path();
 use Sys::Hostname::FQDN();
 use Sys::Hostname::FQDN();

+ 1 - 1
lib/Trog/Email.pm

@@ -1,7 +1,7 @@
 package Trog::Email;
 package Trog::Email;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Email::MIME;
 use Email::MIME;
 use Email::Sender::Simple;
 use Email::Sender::Simple;

+ 1 - 1
lib/Trog/Enrich/Invoice.pm

@@ -1,7 +1,7 @@
 package Trog::Enrich::Invoice;
 package Trog::Enrich::Invoice;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use List::Util();
 use List::Util();
 
 

+ 1 - 1
lib/Trog/FileHandler.pm

@@ -1,7 +1,7 @@
 package Trog::FileHandler;
 package Trog::FileHandler;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use POSIX qw{strftime};
 use POSIX qw{strftime};
 use IO::Compress::Gzip;
 use IO::Compress::Gzip;

+ 1 - 1
lib/Trog/Log.pm

@@ -1,7 +1,7 @@
 package Trog::Log;
 package Trog::Log;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use POSIX qw{strftime};
 use POSIX qw{strftime};
 use Module::Runtime();
 use Module::Runtime();

+ 3 - 3
lib/Trog/Log/DBI.pm

@@ -1,7 +1,7 @@
 package Trog::Log::DBI;
 package Trog::Log::DBI;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use parent qw{Log::Dispatch::DBI};
 use parent qw{Log::Dispatch::DBI};
 
 
@@ -82,11 +82,11 @@ sub log_message {
     # Rip apart the message.  If it's got any extended info, lets grab that too.
     # Rip apart the message.  If it's got any extended info, lets grab that too.
     my $msg = $params{message};
     my $msg = $params{message};
     my $message;
     my $message;
-    my ( $date, $uuid, $ip, $user, $method, $code, $bytes, $route ) = $msg =~ m!^([\w|\-|:]+) \[INFO\]: RequestId ([\w|\-]+) From ([\w|\.|:]+) \|(\w+)\| (\w+) (\d+) (\d+) (.+)!;
+    my ( $date, $uuid, $ip, $user, $method, $code, $bytes, $route ) = $msg =~ m!^([\w|\-|:]+)[ ]\[INFO\]:[ ]RequestId[ ]([\w|\-]+)[ ]From[ ]([\w|\.|:]+)[ ]\|(\w+)\|[ ](\w+)[ ](\d+)[ ](\d+)[ ](\N+)!;
 
 
     # Otherwise, let's mark it down in the "messages" table.  This will be deferred until the final write.
     # Otherwise, let's mark it down in the "messages" table.  This will be deferred until the final write.
     if ( !$date ) {
     if ( !$date ) {
-        ( $date, $uuid, $ip, $user, $message ) = $msg =~ m!^([\w|\-|:]+) \[\w+\]: RequestId ([\w|\-]+) From ([\w|\.|:]+) \|(\w+)\| (.+)!;
+        ( $date, $uuid, $ip, $user, $message ) = $msg =~ m!^([\w|\-|:]+)[ ]\[\w+\]:[ ]RequestId[ ]([\w|\-]+)[ ]From[ ]([\w|\.|:]+)[ ]\|(\w+)\|[ ](\N+)!;
 
 
         # If we can't figure out its request, ignore the message
         # If we can't figure out its request, ignore the message
         if ( length $uuid ) {
         if ( length $uuid ) {

+ 1 - 1
lib/Trog/Log/Metrics.pm

@@ -1,7 +1,7 @@
 package Trog::Log::Metrics;
 package Trog::Log::Metrics;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Trog::SQLite;
 use Trog::SQLite;
 use Trog::Log;
 use Trog::Log;

+ 1 - 1
lib/Trog/Log/Syslog.pm

@@ -1,7 +1,7 @@
 package Trog::Log::Syslog;
 package Trog::Log::Syslog;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use parent qw{Log::Dispatch::Output};
 use parent qw{Log::Dispatch::Output};
 
 

+ 1 - 1
lib/Trog/Renderer.pm

@@ -1,7 +1,7 @@
 package Trog::Renderer;
 package Trog::Renderer;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Carp::Always;
 use Carp::Always;
 
 

+ 1 - 1
lib/Trog/Renderer/Base.pm

@@ -1,7 +1,7 @@
 package Trog::Renderer::Base;
 package Trog::Renderer::Base;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Encode qw{encode_utf8};
 use Encode qw{encode_utf8};
 use IO::Compress::Gzip;
 use IO::Compress::Gzip;

+ 1 - 1
lib/Trog/Renderer/blob.pm

@@ -1,7 +1,7 @@
 package Trog::Renderer::blob;
 package Trog::Renderer::blob;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 =head1 Trog::Renderer::blob
 =head1 Trog::Renderer::blob
 
 

+ 1 - 1
lib/Trog/Renderer/css.pm

@@ -1,7 +1,7 @@
 package Trog::Renderer::css;
 package Trog::Renderer::css;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use parent qw{Trog::Renderer::Base};
 use parent qw{Trog::Renderer::Base};
 
 

+ 1 - 1
lib/Trog/Renderer/email.pm

@@ -1,7 +1,7 @@
 package Trog::Renderer::email;
 package Trog::Renderer::email;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use parent qw{Trog::Renderer::Base};
 use parent qw{Trog::Renderer::Base};
 
 

+ 1 - 1
lib/Trog/Renderer/html.pm

@@ -1,7 +1,7 @@
 package Trog::Renderer::html;
 package Trog::Renderer::html;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use parent qw{Trog::Renderer::Base};
 use parent qw{Trog::Renderer::Base};
 
 

+ 1 - 1
lib/Trog/Renderer/javascript.pm

@@ -1,7 +1,7 @@
 package Trog::Renderer::javascript;
 package Trog::Renderer::javascript;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use parent qw{Trog::Renderer::Base};
 use parent qw{Trog::Renderer::Base};
 
 

+ 1 - 1
lib/Trog/Renderer/json.pm

@@ -1,7 +1,7 @@
 package Trog::Renderer::json;
 package Trog::Renderer::json;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use JSON::MaybeXS;
 use JSON::MaybeXS;
 
 

+ 1 - 1
lib/Trog/Renderer/text.pm

@@ -1,7 +1,7 @@
 package Trog::Renderer::text;
 package Trog::Renderer::text;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use parent qw{Trog::Renderer::Base};
 use parent qw{Trog::Renderer::Base};
 
 

+ 1 - 1
lib/Trog/Routes/HTML.pm

@@ -1,7 +1,7 @@
 package Trog::Routes::HTML;
 package Trog::Routes::HTML;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 no warnings qw{once};
 no warnings qw{once};
 
 

+ 1 - 1
lib/Trog/Routes/JSON.pm

@@ -1,7 +1,7 @@
 package Trog::Routes::JSON;
 package Trog::Routes::JSON;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Clone qw{clone};
 use Clone qw{clone};
 use JSON::MaybeXS();
 use JSON::MaybeXS();

+ 1 - 1
lib/Trog/Routes/TXT.pm

@@ -1,7 +1,7 @@
 package Trog::Routes::JSON;
 package Trog::Routes::JSON;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Clone qw{clone};
 use Clone qw{clone};
 use JSON::MaybeXS();
 use JSON::MaybeXS();

+ 1 - 1
lib/Trog/SQLite/TagIndex.pm

@@ -1,7 +1,7 @@
 package Trog::SQLite::TagIndex;
 package Trog::SQLite::TagIndex;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use URI::Escape;
 use URI::Escape;
 use List::Util qw{uniq};
 use List::Util qw{uniq};

+ 1 - 1
lib/Trog/Themes.pm

@@ -1,7 +1,7 @@
 package Trog::Themes;
 package Trog::Themes;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use FindBin::libs;
 use FindBin::libs;
 
 

+ 1 - 1
lib/Trog/Utils.pm

@@ -1,7 +1,7 @@
 package Trog::Utils;
 package Trog::Utils;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use UUID;
 use UUID;
 use HTTP::Tiny::UNIX();
 use HTTP::Tiny::UNIX();

+ 1 - 1
lib/Trog/Vars.pm

@@ -1,7 +1,7 @@
 package Trog::Vars;
 package Trog::Vars;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 =head1 Trog::Vars
 =head1 Trog::Vars
 
 

+ 1 - 1
lib/Trog/Vault.pm

@@ -1,7 +1,7 @@
 package Trog::Vault;
 package Trog::Vault;
 
 
 use 5.040;
 use 5.040;
-use re '/aa';
+use re '/aasx';
 
 
 use Crypt::AuthEnc::GCM  ();
 use Crypt::AuthEnc::GCM  ();
 use Crypt::KeyDerivation ();
 use Crypt::KeyDerivation ();

+ 1 - 0
t/Trog-Auth-libravatar.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;

+ 1 - 0
t/Trog-Auth-totp-mandatory.t

@@ -5,6 +5,7 @@
 # and about the fact that there is no longer any way to turn 2fa back off.
 # and about the fact that there is no longer any way to turn 2fa back off.
 
 
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use FindBin;
 use FindBin;

+ 8 - 7
t/Trog-Component-page.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;
@@ -50,8 +51,8 @@ subtest 'Header' => sub {
         }
         }
     );
     );
 
 
-    like( $out, qr/<!doctype html>/i,              "the doctype is emitted by default" );
-    like( $out, qr{<title>A Page</title>},         "the title lands" );
+    like( $out, qr/<!doctype[ ]html>/i,            "the doctype is emitted by default" );
+    like( $out, qr{<title>A[ ]Page</title>},       "the title lands" );
     like( $out, qr{href="/styles/screen\.css"},    "stylesheets land" );
     like( $out, qr{href="/styles/screen\.css"},    "stylesheets land" );
     like( $out, qr{src="/scripts/post\.js"},       "scripts land" );
     like( $out, qr{src="/scripts/post\.js"},       "scripts land" );
     like( $out, qr{lang="en-US"},                  "lang defaults rather than rendering empty" );
     like( $out, qr{lang="en-US"},                  "lang defaults rather than rendering empty" );
@@ -62,11 +63,11 @@ subtest 'Header' => sub {
     unlike( $xsl, qr/<!doctype/i, "no_doctype suppresses it" );
     unlike( $xsl, qr/<!doctype/i, "no_doctype suppresses it" );
 
 
     # It also has no stylesheets or scripts to give, and must not invent any.
     # It also has no stylesheets or scripts to give, and must not invent any.
-    unlike( $xsl, qr/rel="stylesheet" type="text\/css" href="\/styles\/screen/, "no stylesheets are conjured up" );
+    unlike( $xsl, qr/rel="stylesheet"[ ]type="text\/css"[ ]href="\/styles\/screen/, "no stylesheets are conjured up" );
 
 
     # header.tx marks meta_tags raw, so social markup has to survive as markup.
     # header.tx marks meta_tags raw, so social markup has to survive as markup.
     my $social = Trog::Component::component( 'Header', { meta_tags => '<meta name="twitter:card" />' } );
     my $social = Trog::Component::component( 'Header', { meta_tags => '<meta name="twitter:card" />' } );
-    like( $social, qr{<meta name="twitter:card" />}, "meta_tags stay markup" );
+    like( $social, qr{<meta[ ]name="twitter:card"[ ]/>}, "meta_tags stay markup" );
 };
 };
 
 
 subtest 'CategoryBar' => sub {
 subtest 'CategoryBar' => sub {
@@ -88,7 +89,7 @@ subtest 'PostHeader and PostFooter validate the name' => sub {
     foreach my $bad ( '../../etc/passwd', 'headers/../../../etc/passwd', 'nope.tx', 'about_header' ) {
     foreach my $bad ( '../../etc/passwd', 'headers/../../../etc/passwd', 'nope.tx', 'about_header' ) {
         like(
         like(
             exception { Trog::Component::component( 'PostHeader', { name => $bad } ) },
             exception { Trog::Component::component( 'PostHeader', { name => $bad } ) },
-            qr/No such header/,
+            qr/No[ ]such[ ]header/,
             "'$bad' is refused"
             "'$bad' is refused"
         );
         );
     }
     }
@@ -138,9 +139,9 @@ subtest 'the whole page still composes' => sub {
     is( $err, '', "index.tx renders" ) or diag($err);
     is( $err, '', "index.tx renders" ) or diag($err);
     my $body = ref $psgi eq 'ARRAY' ? $psgi->[2][0] : undef;
     my $body = ref $psgi eq 'ARRAY' ? $psgi->[2][0] : undef;
 
 
-    like( $body, qr/<!doctype html>/i,        "Header came through" );
+    like( $body, qr/<!doctype[ ]html>/i,      "Header came through" );
     like( $body, qr{<title>Composed</title>}, "the title came through" );
     like( $body, qr{<title>Composed</title>}, "the title came through" );
-    like( $body, qr{<p>the body</p>},         "the content is still the content" );
+    like( $body, qr{<p>the[ ]body</p>},       "the content is still the content" );
     like( $body, qr{href="/series/blog"},     "CategoryBar came through" );
     like( $body, qr{href="/series/blog"},     "CategoryBar came through" );
     like( $body, qr{</html>},                 "Footer came through" );
     like( $body, qr{</html>},                 "Footer came through" );
 
 

+ 8 - 7
t/Trog-Component.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;
@@ -62,12 +63,12 @@ subtest 'name validation' => sub {
     foreach my $case ( sort keys %bad ) {
     foreach my $case ( sort keys %bad ) {
         like(
         like(
             exception { Trog::Component::component( $bad{$case} ) },
             exception { Trog::Component::component( $bad{$case} ) },
-            qr/component name/i,
+            qr/component[ ]name/i,
             "$case rejected before anything is loaded"
             "$case rejected before anything is loaded"
         );
         );
     }
     }
 
 
-    like( exception { Trog::Component::component(undef) }, qr/component name/i, "undef rejected" );
+    like( exception { Trog::Component::component(undef) }, qr/component[ ]name/i, "undef rejected" );
 
 
     # Nothing above should have made it as far as %INC.
     # Nothing above should have made it as far as %INC.
     is( scalar( grep { m{^Trog/Component/} && !m{^Trog/Component/Test} } keys %INC ), 0, "no stray component was loaded" );
     is( scalar( grep { m{^Trog/Component/} && !m{^Trog/Component/Test} } keys %INC ), 0, "no stray component was loaded" );
@@ -82,7 +83,7 @@ subtest 'loading' => sub {
 
 
     like(
     like(
         exception { Trog::Component::component('TestNoRender') },
         exception { Trog::Component::component('TestNoRender') },
-        qr/no render/i,
+        qr/no[ ]render/i,
         "a module without render() is not a component"
         "a module without render() is not a component"
     );
     );
 };
 };
@@ -108,13 +109,13 @@ subtest 'failure modes' => sub {
     # meaningless halfway through a template.
     # meaningless halfway through a template.
     like(
     like(
         exception { Trog::Component::component('TestTriplet') },
         exception { Trog::Component::component('TestTriplet') },
-        qr/TestTriplet.*ARRAY/,
+        qr/TestTriplet\N*ARRAY/,
         "a PSGI triplet is caught rather than leaking into the page"
         "a PSGI triplet is caught rather than leaking into the page"
     );
     );
 
 
     like(
     like(
         exception { Trog::Component::component('TestSilent') },
         exception { Trog::Component::component('TestSilent') },
-        qr/rendered nothing/,
+        qr/rendered[ ]nothing/,
         "an undef render is a failure, not an empty component"
         "an undef render is a failure, not an empty component"
     );
     );
 
 
@@ -130,7 +131,7 @@ subtest 'recursion guard' => sub {
     local $Trog::Component::MAX_DEPTH = 5;
     local $Trog::Component::MAX_DEPTH = 5;
 
 
     my $err = exception { Trog::Component::component('TestRecurse') };
     my $err = exception { Trog::Component::component('TestRecurse') };
-    like( $err, qr/recursed more than 5 deep/, "self-referential component dies bounded" );
+    like( $err, qr/recursed[ ]more[ ]than[ ]5[ ]deep/, "self-referential component dies bounded" );
 
 
     is( $Trog::Component::depth, 0, "depth unwinds after the die" );
     is( $Trog::Component::depth, 0, "depth unwinds after the die" );
 };
 };
@@ -171,7 +172,7 @@ subtest 'a failure inside a template is not swallowed' => sub {
 
 
     my ( $out, $err ) = $render->(q{before<: component('../../etc/passwd') :>after});
     my ( $out, $err ) = $render->(q{before<: component('../../etc/passwd') :>after});
     is( $out, 'beforeafter', "Xslate really does render on past a dead component" );
     is( $out, 'beforeafter', "Xslate really does render on past a dead component" );
-    like( $err, qr/component name/i, "...and the failure is recorded for the renderer to find" );
+    like( $err, qr/component[ ]name/i, "...and the failure is recorded for the renderer to find" );
 
 
     ( $out, $err ) = $render->(q{<: component('TestBoom') :>});
     ( $out, $err ) = $render->(q{<: component('TestBoom') :>});
     like( $err, qr/TestBoom/, "a component's own death is recorded" );
     like( $err, qr/TestBoom/, "a component's own death is recorded" );

+ 1 - 0
t/Trog-Config.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;

+ 4 - 3
t/Trog-Data-FlatFile-read.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;
@@ -109,7 +110,7 @@ subtest 'the log works before log_init()' => sub {
 
 
     @said = ();
     @said = ();
     is( exception { Trog::Log::WARN('a warning') }, undef, "WARN does not die" );
     is( exception { Trog::Log::WARN('a warning') }, undef, "WARN does not die" );
-    like( join( '', @said ), qr/\[WARN\].*a warning/, "it lands on stderr instead" );
+    like( join( '', @said ), qr/\[WARN\]\N*a[ ]warning/, "it lands on stderr instead" );
 
 
     @said = ();
     @said = ();
     is( exception { Trog::Log::INFO('a note') },    undef, "INFO does not die" );
     is( exception { Trog::Log::INFO('a note') },    undef, "INFO does not die" );
@@ -118,10 +119,10 @@ subtest 'the log works before log_init()' => sub {
     # An uninitialized request id used to be spliced into the line as an
     # An uninitialized request id used to be spliced into the line as an
     # uninitialized-value warning.
     # uninitialized-value warning.
     unlike( join( '', @said ), qr/uninitialized/, "and no uninitialized value creeps in" );
     unlike( join( '', @said ), qr/uninitialized/, "and no uninitialized value creeps in" );
-    like( join( '', @said ), qr/RequestId NONE/, "the request id says it has none" );
+    like( join( '', @said ), qr/RequestId[ ]NONE/, "the request id says it has none" );
 
 
     # FATAL is fatal either way, or the caller's error handling disappears.
     # FATAL is fatal either way, or the caller's error handling disappears.
-    like( exception { Trog::Log::FATAL('the roof is on fire') }, qr/the roof is on fire/, "FATAL still dies" );
+    like( exception { Trog::Log::FATAL('the roof is on fire') }, qr/the[ ]roof[ ]is[ ]on[ ]fire/, "FATAL still dies" );
 };
 };
 
 
 done_testing();
 done_testing();

+ 5 - 4
t/Trog-Data-SQLite.t

@@ -6,6 +6,7 @@
 # both models and asserts they return the same posts for the same query.
 # both models and asserts they return the same posts for the same query.
 
 
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use FindBin;
 use FindBin;
@@ -229,7 +230,7 @@ subtest 'a caller who can see nothing is not queried for' => sub {
 
 
     is( exception { is_deeply( $sqlite->read( { limit => 0, acls => [] } ), [], 'no acls get nothing' ) }, undef, 'without asking the database' );
     is( exception { is_deeply( $sqlite->read( { limit => 0, acls => [] } ), [], 'no acls get nothing' ) }, undef, 'without asking the database' );
     is( exception { is_deeply( $sqlite->read( { limit => 0, acls => [], id => 'p-public' } ), [], 'nor by id' ) }, undef, 'nor for a direct lookup' );
     is( exception { is_deeply( $sqlite->read( { limit => 0, acls => [], id => 'p-public' } ), [], 'nor by id' ) }, undef, 'nor for a direct lookup' );
-    like( exception { $sqlite->read( { limit => 0, acls => ['public'] } ) }, qr/queried the database/a, 'while a caller with acls is queried' );
+    like( exception { $sqlite->read( { limit => 0, acls => ['public'] } ) }, qr/queried[ ]the[ ]database/, 'while a caller with acls is queried' );
 };
 };
 
 
 subtest 'versions' => sub {
 subtest 'versions' => sub {
@@ -344,13 +345,13 @@ subtest 'writing' => sub {
         local $SIG{__WARN__} = sub { };
         local $SIG{__WARN__} = sub { };
         exception { $sqlite->write( [ post( id => 'p-new', title => 'Clashing' ) ] ) };
         exception { $sqlite->write( [ post( id => 'p-new', title => 'Clashing' ) ] ) };
     };
     };
-    like( $clash, qr/Could not write post 'p-new'/, 'the same version twice is refused, loudly' );
+    like( $clash, qr/Could[ ]not[ ]write[ ]post[ ]'p-new'/, 'the same version twice is refused, loudly' );
 
 
     is( exception { $sqlite->write( [ post( id => 'p-new', title => 'Second Version', version => 1 ) ] ) }, undef, 'a new version is not' );
     is( exception { $sqlite->write( [ post( id => 'p-new', title => 'Second Version', version => 1 ) ] ) }, undef, 'a new version is not' );
     my ($now) = $sqlite->get( limit => 0, acls => [qw{admin}], id => 'p-new' );
     my ($now) = $sqlite->get( limit => 0, acls => [qw{admin}], id => 'p-new' );
     is( $now->{title}, 'Second Version', 'and wins' );
     is( $now->{title}, 'Second Version', 'and wins' );
 
 
-    like( exception { $sqlite->write( [ { title => 'No Id' } ] ) }, qr/no id/i, 'a post with no id is refused' );
+    like( exception { $sqlite->write( [ { title => 'No Id' } ] ) }, qr/no[ ]id/i, 'a post with no id is refused' );
 
 
     my $count = $sqlite->count();
     my $count = $sqlite->count();
     ok( exception { $sqlite->write( [ post( id => 'p-half', title => 'Half' ), { title => 'No Id' } ] ) }, 'a batch with a post it refuses is refused' );
     ok( exception { $sqlite->write( [ post( id => 'p-half', title => 'Half' ), { title => 'No Id' } ] ) }, 'a batch with a post it refuses is refused' );
@@ -469,7 +470,7 @@ subtest 'indexing a custom field' => sub {
 
 
     # And SQLite actually uses it, which is the entire point.
     # And SQLite actually uses it, which is the entire point.
     my ($plan) = map { $_->{detail} } @{ $dbh->selectall_arrayref( q{EXPLAIN QUERY PLAN SELECT id FROM posts WHERE cook_time = '45 minutes'}, { Slice => {} } ) };
     my ($plan) = map { $_->{detail} } @{ $dbh->selectall_arrayref( q{EXPLAIN QUERY PLAN SELECT id FROM posts WHERE cook_time = '45 minutes'}, { Slice => {} } ) };
-    like( $plan, qr/USING INDEX posts_custom_cook_time/, 'and searches it rather than every post' );
+    like( $plan, qr/USING[ ]INDEX[ ]posts_custom_cook_time/, 'and searches it rather than every post' );
 
 
     is( exception { $sqlite->index_fields(qw{cook_time}) }, undef, 'asking twice is not an error' );
     is( exception { $sqlite->index_fields(qw{cook_time}) }, undef, 'asking twice is not an error' );
 
 

+ 2 - 1
t/Trog-Data.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;
@@ -45,7 +46,7 @@ subtest 'model' => sub {
     $dir->child( 'files', 'another' )->spew('{}');
     $dir->child( 'files', 'another' )->spew('{}');
     is( Trog::Data::model( config_for('SQLite') ), 'FlatFile', 'a site with flat file posts and no database keeps serving its posts' );
     is( Trog::Data::model( config_for('SQLite') ), 'FlatFile', 'a site with flat file posts and no database keeps serving its posts' );
     is( scalar(@warned),                           1,          'with a warning' );
     is( scalar(@warned),                           1,          'with a warning' );
-    like( $warned[0] // '', qr{holds 2 post\(s\).*bin/migrate\.pl}a, 'saying how many, and how to move them' );
+    like( $warned[0] // '', qr{holds[ ]2[ ]post\(s\)\N*bin/migrate\.pl}, 'saying how many, and how to move them' );
 
 
     $dir->child('posts.sqlite')->touch;
     $dir->child('posts.sqlite')->touch;
     @warned = ();
     @warned = ();

+ 7 - 6
t/Trog-DataModule-schema.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;
@@ -290,7 +291,7 @@ subtest 'the custom types actually bite' => sub {
     set_sidecars();
     set_sidecars();
 
 
     my ($bad) = Trog::DataModule::validate( { title => 'x', callback => 'Some::Random::Sub' } );
     my ($bad) = Trog::DataModule::validate( { title => 'x', callback => 'Some::Random::Sub' } );
-    like( $bad, qr/cannot be loaded/, "a callback naming a module that isn't there is refused" );
+    like( $bad, qr/cannot[ ]be[ ]loaded/, "a callback naming a module that isn't there is refused" );
 
 
     is_deeply(
     is_deeply(
         [ Trog::DataModule::validate( { title => 'x', callback => 'Trog::Routes::HTML::posts' } ) ],
         [ Trog::DataModule::validate( { title => 'x', callback => 'Trog::Routes::HTML::posts' } ) ],
@@ -318,7 +319,7 @@ subtest 'add() rejects a post that does not validate' => sub {
 
 
     # A string die would come back wearing a Carp::Always stack trace, which is
     # A string die would come back wearing a Carp::Always stack trace, which is
     # no use at all to whoever just submitted the form.
     # no use at all to whoever just submitted the form.
-    unlike( join( ' ', @$why ), qr/DataModule\.pm line/, "and carries no stack trace" );
+    unlike( join( ' ', @$why ), qr/DataModule\.pm[ ]line/, "and carries no stack trace" );
     ok( !$written, "nothing was written" );
     ok( !$written, "nothing was written" );
 
 
     bless( {}, 'TestData' )->add( { form => 'recipe.tx', title => 'Cookies', visibility => 'public', servings => '4' } );
     bless( {}, 'TestData' )->add( { form => 'recipe.tx', title => 'Cookies', visibility => 'public', servings => '4' } );
@@ -358,9 +359,9 @@ subtest 'a save message never becomes a URL' => sub {
     my $realistic = "Couldn't rename data/dnZHxmlXE5 to data/files/: Not a directory at /opt/perl5/lib/File/Slurper/Temp.pm line 56.\n" . "\tFile::Slurper::Temp::write_text(\"data/files/\", \"[{...}]\") called at /opt/perl5/lib/File/Slurper/Temp.pm line 63\n" . ( "\tTrog::Routes::HTML::post_save(HASH(0x5e798a626570)) called at lib/TCMS.pm line 130\n" x 40 );
     my $realistic = "Couldn't rename data/dnZHxmlXE5 to data/files/: Not a directory at /opt/perl5/lib/File/Slurper/Temp.pm line 56.\n" . "\tFile::Slurper::Temp::write_text(\"data/files/\", \"[{...}]\") called at /opt/perl5/lib/File/Slurper/Temp.pm line 63\n" . ( "\tTrog::Routes::HTML::post_save(HASH(0x5e798a626570)) called at lib/TCMS.pm line 130\n" x 40 );
 
 
     my $short = Trog::Routes::HTML::_short_error($realistic);
     my $short = Trog::Routes::HTML::_short_error($realistic);
-    unlike( $short, qr/\n/,       'the trace is gone' );
-    unlike( $short, qr/line \d+/, 'and so is the file and line Perl glued on' );
-    like( $short, qr/^Couldn't rename data/, 'leaving what actually went wrong' );
+    unlike( $short, qr/\n/,         'the trace is gone' );
+    unlike( $short, qr/line[ ]\d+/, 'and so is the file and line Perl glued on' );
+    like( $short, qr/^Couldn't[ ]rename[ ]data/, 'leaving what actually went wrong' );
 
 
     my $cookie = Trog::Routes::HTML::_feedback_cookie( 1, $realistic );
     my $cookie = Trog::Routes::HTML::_feedback_cookie( 1, $realistic );
     ok( length($cookie) < 2048, 'even the whole untrimmed thing fits in a cookie' )
     ok( length($cookie) < 2048, 'even the whole untrimmed thing fits in a cookie' )
@@ -376,7 +377,7 @@ subtest 'a save message never becomes a URL' => sub {
     # Round trips.
     # Round trips.
     my ( $failure, $message ) = Trog::Routes::HTML::_feedback_from_cookie($cookie);
     my ( $failure, $message ) = Trog::Routes::HTML::_feedback_from_cookie($cookie);
     is( $failure, 1, 'the outcome comes back out' );
     is( $failure, 1, 'the outcome comes back out' );
-    like( $message, qr/^Couldn't rename/, 'and so does the message' );
+    like( $message, qr/^Couldn't[ ]rename/, 'and so does the message' );
 
 
     ( $failure, $message ) = Trog::Routes::HTML::_feedback_from_cookie( Trog::Routes::HTML::_feedback_cookie( 0, "Saved post 'Thing'." ) );
     ( $failure, $message ) = Trog::Routes::HTML::_feedback_from_cookie( Trog::Routes::HTML::_feedback_cookie( 0, "Saved post 'Thing'." ) );
     is( $failure, 0,                     'a success comes back as one' );
     is( $failure, 0,                     'a success comes back as one' );

+ 1 - 0
t/Trog-DataSource-DirIndex.t

@@ -5,6 +5,7 @@
 # getting it wrong hands out the names of files somebody meant to keep.
 # getting it wrong hands out the names of files somebody meant to keep.
 
 
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use FindBin;
 use FindBin;

+ 14 - 13
t/Trog-DataSource-ProvisionedVirt.t

@@ -5,6 +5,7 @@
 # throughout: nothing here forks, and nothing here executes a provisioner.
 # throughout: nothing here forks, and nothing here executes a provisioner.
 
 
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use FindBin;
 use FindBin;
@@ -176,12 +177,12 @@ subtest 'the recipe lands on the guest' => sub {
 subtest 'what it refuses to reprovision' => sub {
 subtest 'what it refuses to reprovision' => sub {
     my ( $ok, $why ) = reprovision( domain => 'thisserver.example.com' );
     my ( $ok, $why ) = reprovision( domain => 'thisserver.example.com' );
     is( $ok, 0, 'the server it is running on' );
     is( $ok, 0, 'the server it is running on' );
-    like( $why, qr/this server/, 'and says why' );
+    like( $why, qr/this[ ]server/, 'and says why' );
     is_deeply( \@SPAWNED, [], 'without running anything' );
     is_deeply( \@SPAWNED, [], 'without running anything' );
 
 
     ( $ok, $why ) = reprovision( domain => 'norecipe.example.com' );
     ( $ok, $why ) = reprovision( domain => 'norecipe.example.com' );
     is( $ok, 0, 'a guest with no recipe' );
     is( $ok, 0, 'a guest with no recipe' );
-    like( $why, qr/no recipe/, 'and says why' );
+    like( $why, qr/no[ ]recipe/, 'and says why' );
 
 
     ( $ok, $why ) = reprovision( domain => 'guest.example.com', passphrase => '' );
     ( $ok, $why ) = reprovision( domain => 'guest.example.com', passphrase => '' );
     is( $ok, 0, 'a run with no passphrase, which the provisioner would just hang waiting for' );
     is( $ok, 0, 'a run with no passphrase, which the provisioner would just hang waiting for' );
@@ -191,7 +192,7 @@ subtest 'what it refuses to reprovision' => sub {
     foreach my $bad ( '../../etc/passwd', 'guest;rm -rf /', 'guest example.com', '', 'a' x 300, '.hidden' ) {
     foreach my $bad ( '../../etc/passwd', 'guest;rm -rf /', 'guest example.com', '', 'a' x 300, '.hidden' ) {
         my ( $refused, $message ) = reprovision( domain => $bad );
         my ( $refused, $message ) = reprovision( domain => $bad );
         is( $refused, 0, "'$bad' is refused" );
         is( $refused, 0, "'$bad' is refused" );
-        like( $message, qr/bad guest name|no recipe/, '...before anything is run' );
+        like( $message, qr/bad[ ]guest[ ]name|no[ ]recipe/, '...before anything is run' );
     }
     }
 
 
     is_deeply( \@SPAWNED, [], 'and none of that ran a provisioner' );
     is_deeply( \@SPAWNED, [], 'and none of that ran a provisioner' );
@@ -257,7 +258,7 @@ subtest 'a stored passphrase, and a code instead of one' => sub {
     # somebody can click inside the window.
     # somebody can click inside the window.
     ( $ok, $why ) = reprovision( domain => 'guest.example.com', passphrase => undef, totp => $code );
     ( $ok, $why ) = reprovision( domain => 'guest.example.com', passphrase => undef, totp => $code );
     is( $ok, 0, 'and cannot be used again' );
     is( $ok, 0, 'and cannot be used again' );
-    like( $why, qr/already been used/, 'saying so' );
+    like( $why, qr/already[ ]been[ ]used/, 'saying so' );
     is_deeply( \@SPAWNED, [], 'without running a second one' );
     is_deeply( \@SPAWNED, [], 'without running a second one' );
 
 
     Trog::Vault::forget( 'admin', $Trog::DataSource::ProvisionedVirt::secret_name );
     Trog::Vault::forget( 'admin', $Trog::DataSource::ProvisionedVirt::secret_name );
@@ -320,12 +321,12 @@ subtest 'a password with a line break in it is refused' => sub {
     # nobody asked us to send.
     # nobody asked us to send.
     my ( $ok, $why ) = reprovision( domain => 'guest.example.com', passphrase => "correct horse\nsudo: injected" );
     my ( $ok, $why ) = reprovision( domain => 'guest.example.com', passphrase => "correct horse\nsudo: injected" );
     is( $ok, 0, 'a passphrase carrying a line break does not run' );
     is( $ok, 0, 'a passphrase carrying a line break does not run' );
-    like( $why, qr/line break/, 'saying so' );
+    like( $why, qr/line[ ]break/, 'saying so' );
     is_deeply( \@SPAWNED, [], 'having run nothing' );
     is_deeply( \@SPAWNED, [], 'having run nothing' );
 
 
     ( $ok, $why ) = reprovision( domain => 'guest.example.com', passphrase => 'correct horse', sudo => "hunter2\nkeepass: not this" );
     ( $ok, $why ) = reprovision( domain => 'guest.example.com', passphrase => 'correct horse', sudo => "hunter2\nkeepass: not this" );
     is( $ok, 0, 'and neither does a sudo password carrying one' );
     is( $ok, 0, 'and neither does a sudo password carrying one' );
-    like( $why, qr/line break/, 'saying so too' );
+    like( $why, qr/line[ ]break/, 'saying so too' );
     is_deeply( \@SPAWNED, [], 'having run nothing' );
     is_deeply( \@SPAWNED, [], 'having run nothing' );
 };
 };
 
 
@@ -359,8 +360,8 @@ subtest 'and when the vault key has gone' => sub {
     my $code = $totp->expected_totp_code( time() );
     my $code = $totp->expected_totp_code( time() );
     my ( $ok, $why ) = reprovision( domain => 'guest.example.com', passphrase => undef, totp => $code );
     my ( $ok, $why ) = reprovision( domain => 'guest.example.com', passphrase => undef, totp => $code );
     is( $ok, 0, 'a code is refused rather than exploding' );
     is( $ok, 0, 'a code is refused rather than exploding' );
-    like( $why, qr/can open/, 'saying the passphrase cannot be opened' );
-    like( $why, qr{/secrets}, 'and where to go about it' );
+    like( $why, qr/can[ ]open/, 'saying the passphrase cannot be opened' );
+    like( $why, qr{/secrets},   'and where to go about it' );
     is_deeply( \@SPAWNED, [], 'having provisioned nothing' );
     is_deeply( \@SPAWNED, [], 'having provisioned nothing' );
 
 
     # And the refusal came before the code was spent, so the same code is still
     # And the refusal came before the code was spent, so the same code is still
@@ -382,7 +383,7 @@ subtest 'without the provisioner configured it is just Virt' => sub {
 
 
     my ( $ok, $why ) = reprovision( domain => 'guest.example.com' );
     my ( $ok, $why ) = reprovision( domain => 'guest.example.com' );
     is( $ok, 0, 'nothing to run' );
     is( $ok, 0, 'nothing to run' );
-    like( $why, qr/not configured/, 'and says so plainly' );
+    like( $why, qr/not[ ]configured/, 'and says so plainly' );
     is_deeply( \@SPAWNED, [], 'having run nothing' );
     is_deeply( \@SPAWNED, [], 'having run nothing' );
 
 
     $virt->redefine( posts => sub { return ( { title => 'guest.example.com', domain => 'guest.example.com', is_self => 0 } ) } );
     $virt->redefine( posts => sub { return ( { title => 'guest.example.com', domain => 'guest.example.com', is_self => 0 } ) } );
@@ -395,7 +396,7 @@ subtest 'without the provisioner configured it is just Virt' => sub {
     write_config( trog_provisioner => "$ROOT/nonesuch" );
     write_config( trog_provisioner => "$ROOT/nonesuch" );
     ( $ok, $why ) = reprovision( domain => 'guest.example.com' );
     ( $ok, $why ) = reprovision( domain => 'guest.example.com' );
     is( $ok, 0, 'a configured path which is not there is refused' );
     is( $ok, 0, 'a configured path which is not there is refused' );
-    like( $why, qr/not configured/, 'the same as never having configured one' );
+    like( $why, qr/not[ ]configured/, 'the same as never having configured one' );
 
 
     write_config( trog_provisioner => "$ROOT/trog-provisioner" );
     write_config( trog_provisioner => "$ROOT/trog-provisioner" );
 };
 };
@@ -410,7 +411,7 @@ subtest 'the log is read back onto the page' => sub {
 
 
     my $tail = Trog::DataSource::ProvisionedVirt::_log_tail('guest.example.com');
     my $tail = Trog::DataSource::ProvisionedVirt::_log_tail('guest.example.com');
     ok( length($tail) <= $Trog::DataSource::ProvisionedVirt::log_tail_bytes, 'only the tail of it' );
     ok( length($tail) <= $Trog::DataSource::ProvisionedVirt::log_tail_bytes, 'only the tail of it' );
-    like( $tail, qr/=== finished ===/, 'which is the part that says how it went' );
+    like( $tail, qr/===[ ]finished[ ]===/, 'which is the part that says how it went' );
 
 
     is( Trog::DataSource::ProvisionedVirt::_log_tail('norecipe.example.com'), undef, 'nothing when it has never run' );
     is( Trog::DataSource::ProvisionedVirt::_log_tail('norecipe.example.com'), undef, 'nothing when it has never run' );
     is( Trog::DataSource::ProvisionedVirt::_log_tail('../../etc/passwd'),     undef, 'and a name that is not one reads nothing' );
     is( Trog::DataSource::ProvisionedVirt::_log_tail('../../etc/passwd'),     undef, 'and a name that is not one reads nothing' );
@@ -493,7 +494,7 @@ subtest 'the listing says what is going on' => sub {
     # Starting a second one is refused for the same reason.
     # Starting a second one is refused for the same reason.
     my ( $ok, $why ) = reprovision( domain => 'guest.example.com' );
     my ( $ok, $why ) = reprovision( domain => 'guest.example.com' );
     is( $ok, 0, 'and a second run is refused while the first is going' );
     is( $ok, 0, 'and a second run is refused while the first is going' );
-    like( $why, qr/already running/, 'saying so' );
+    like( $why, qr/already[ ]running/, 'saying so' );
     is_deeply( \@SPAWNED, [], 'having started nothing' );
     is_deeply( \@SPAWNED, [], 'having started nothing' );
 
 
     open( $fh, '>', $status ) or die $!;
     open( $fh, '>', $status ) or die $!;
@@ -522,7 +523,7 @@ subtest 'reading the log' => sub {
     open( my $fh, '>', "$ROOT/logs/reprovision/guest.example.com.log" ) or die $!;
     open( my $fh, '>', "$ROOT/logs/reprovision/guest.example.com.log" ) or die $!;
     print {$fh} "the whole log\n";
     print {$fh} "the whole log\n";
     close $fh;
     close $fh;
-    like( Trog::DataSource::ProvisionedVirt::log_for('guest.example.com'), qr/the whole log/, 'and the whole of it when there has' );
+    like( Trog::DataSource::ProvisionedVirt::log_for('guest.example.com'), qr/the[ ]whole[ ]log/, 'and the whole of it when there has' );
 
 
     # A provisioner can be chatty, and this goes out in one response.
     # A provisioner can be chatty, and this goes out in one response.
     local $Trog::DataSource::ProvisionedVirt::log_max_bytes = 64;
     local $Trog::DataSource::ProvisionedVirt::log_max_bytes = 64;

+ 9 - 8
t/Trog-DataSource-Virt.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;
@@ -173,15 +174,15 @@ subtest 'act() refuses what it should' => sub {
 
 
     my ( $ok, $message ) = Trog::DataSource::Virt::act( 'rm-rf', 'test:///default', 'alpha', 'specadmin' );
     my ( $ok, $message ) = Trog::DataSource::Virt::act( 'rm-rf', 'test:///default', 'alpha', 'specadmin' );
     ok( !$ok, 'an action we do not define is refused' );
     ok( !$ok, 'an action we do not define is refused' );
-    like( $message, qr/not something we do/, 'and says so' );
+    like( $message, qr/not[ ]something[ ]we[ ]do/, 'and says so' );
 
 
     ( $ok, $message ) = Trog::DataSource::Virt::act( 'destroy', 'test:///default', '../../etc/passwd', 'specadmin' );
     ( $ok, $message ) = Trog::DataSource::Virt::act( 'destroy', 'test:///default', '../../etc/passwd', 'specadmin' );
     ok( !$ok, 'a guest name that is a path is refused' );
     ok( !$ok, 'a guest name that is a path is refused' );
-    like( $message, qr/bad guest name/, 'before any connection is made' );
+    like( $message, qr/bad[ ]guest[ ]name/, 'before any connection is made' );
 
 
     ( $ok, $message ) = Trog::DataSource::Virt::act( 'destroy', 'test:///default', 'nosuchguest', 'specadmin' );
     ( $ok, $message ) = Trog::DataSource::Virt::act( 'destroy', 'test:///default', 'nosuchguest', 'specadmin' );
     ok( !$ok, 'a guest that is not there is refused' );
     ok( !$ok, 'a guest that is not there is refused' );
-    like( $message, qr/no guest called/, 'rather than acting on some other one' );
+    like( $message, qr/no[ ]guest[ ]called/, 'rather than acting on some other one' );
 
 
     is_deeply( \@CALLS, [], 'and none of those touched a guest' );
     is_deeply( \@CALLS, [], 'and none of those touched a guest' );
 
 
@@ -189,7 +190,7 @@ subtest 'act() refuses what it should' => sub {
     ( $ok, $message ) = Trog::DataSource::Virt::act( 'poweroff', 'test:///default', 'alpha', 'specadmin' );
     ( $ok, $message ) = Trog::DataSource::Virt::act( 'poweroff', 'test:///default', 'alpha', 'specadmin' );
     $connect_fails = 0;
     $connect_fails = 0;
     ok( !$ok, 'an unreachable hypervisor is refused' );
     ok( !$ok, 'an unreachable hypervisor is refused' );
-    like( $message, qr/could not reach/, 'and reported' );
+    like( $message, qr/could[ ]not[ ]reach/, 'and reported' );
     is_deeply( \@CALLS, [], 'having done nothing' );
     is_deeply( \@CALLS, [], 'having done nothing' );
 };
 };
 
 
@@ -204,7 +205,7 @@ subtest 'act() will not take the server down with it' => sub {
         @CALLS = ();
         @CALLS = ();
         my ( $ok, $message ) = Trog::DataSource::Virt::act( $action, 'test:///default', 'alpha', 'specadmin' );
         my ( $ok, $message ) = Trog::DataSource::Virt::act( $action, 'test:///default', 'alpha', 'specadmin' );
         ok( !$ok, "$action on the guest we are running in is refused" );
         ok( !$ok, "$action on the guest we are running in is refused" );
-        like( $message, qr/is this server/, 'saying why' );
+        like( $message, qr/is[ ]this[ ]server/, 'saying why' );
         is_deeply( \@CALLS, [], "and the $action never reached libvirt" );
         is_deeply( \@CALLS, [], "and the $action never reached libvirt" );
     }
     }
 
 
@@ -229,7 +230,7 @@ subtest 'screenshot() validates its inputs before anything else' => sub {
 
 
     my ( $path, $why ) = Trog::DataSource::Virt::screenshot( $hv, '../../../etc/passwd' );
     my ( $path, $why ) = Trog::DataSource::Virt::screenshot( $hv, '../../../etc/passwd' );
     is( $path, undef, 'a path-shaped guest name gets no screenshot' );
     is( $path, undef, 'a path-shaped guest name gets no screenshot' );
-    like( $why, qr/bad guest name/, 'and is named as the reason' );
+    like( $why, qr/bad[ ]guest[ ]name/, 'and is named as the reason' );
 
 
     ( $path, $why ) = Trog::DataSource::Virt::screenshot( $hv, 'alpha/../../beta' );
     ( $path, $why ) = Trog::DataSource::Virt::screenshot( $hv, 'alpha/../../beta' );
     is( $path, undef, 'nor does one with traversal in the middle' );
     is( $path, undef, 'nor does one with traversal in the middle' );
@@ -238,7 +239,7 @@ subtest 'screenshot() validates its inputs before anything else' => sub {
     # checked as well -- otherwise it is a directory traversal of its own.
     # checked as well -- otherwise it is a directory traversal of its own.
     ( $path, $why ) = Trog::DataSource::Virt::screenshot( { id => '../../..', conn_uri => 'test:///default' }, 'alpha' );
     ( $path, $why ) = Trog::DataSource::Virt::screenshot( { id => '../../..', conn_uri => 'test:///default' }, 'alpha' );
     is( $path, undef, 'nor does a path-shaped hypervisor id' );
     is( $path, undef, 'nor does a path-shaped hypervisor id' );
-    like( $why, qr/bad hypervisor id/, 'which is reported separately' );
+    like( $why, qr/bad[ ]hypervisor[ ]id/, 'which is reported separately' );
 };
 };
 
 
 subtest 'the guest cache is scoped per hypervisor' => sub {
 subtest 'the guest cache is scoped per hypervisor' => sub {
@@ -363,7 +364,7 @@ subtest 'guests show nothing an unprivileged viewer cannot use' => sub {
 
 
         # The screenshot route requires the admin acl, so showing the img to
         # The screenshot route requires the admin acl, so showing the img to
         # anyone else is a broken image and nothing else.
         # anyone else is a broken image and nothing else.
-        is( scalar( () = $out =~ m/<img /g ),                   $admin ? 1 : 0, "$view: console capture shown only to an admin" );
+        is( scalar( () = $out =~ m/<img[ ]/g ),                 $admin ? 1 : 0, "$view: console capture shown only to an admin" );
         is( scalar( () = $out =~ m{href="/guest/screenshot}g ), $admin ? 1 : 0, "$view: and so is the link to it" );
         is( scalar( () = $out =~ m{href="/guest/screenshot}g ), $admin ? 1 : 0, "$view: and so is the link to it" );
 
 
         # Powering off, snapshotting and destroying likewise.
         # Powering off, snapshotting and destroying likewise.

+ 5 - 4
t/Trog-DataSource.t

@@ -6,6 +6,7 @@
 # applied to them by somebody.  This is that somebody.
 # applied to them by somebody.  This is that somebody.
 
 
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use FindBin;
 use FindBin;
@@ -458,7 +459,7 @@ subtest 'the paginator renders page links for a datasource' => sub {
     };
     };
 
 
     my $offset = $render->( paginate_offset => 1, pages => 1, page => 2, pages_total => 5, like => 'web' );
     my $offset = $render->( paginate_offset => 1, pages => 1, page => 2, pages_total => 5, like => 'web' );
-    like( $offset, qr/Page 2 of 5/,             'it says where you are' );
+    like( $offset, qr/Page[ ]2[ ]of[ ]5/,       'it says where you are' );
     like( $offset, qr/href="\?page=1&limit=25/, 'Prev goes back a page' );
     like( $offset, qr/href="\?page=1&limit=25/, 'Prev goes back a page' );
     like( $offset, qr/href="\?page=3&limit=25/, 'Next goes on a page' );
     like( $offset, qr/href="\?page=3&limit=25/, 'Next goes on a page' );
 
 
@@ -466,7 +467,7 @@ subtest 'the paginator renders page links for a datasource' => sub {
     # ampersand in an attribute is supposed to be.  The cursor branch is the same.
     # ampersand in an attribute is supposed to be.  The cursor branch is the same.
     like( $offset, qr/like=web/, 'and the search survives the trip' );
     like( $offset, qr/like=web/, 'and the search survives the trip' );
     unlike( $offset, qr/older=|newer=/, 'no cursor, which could not move on such a page anyway' );
     unlike( $offset, qr/older=|newer=/, 'no cursor, which could not move on such a page anyway' );
-    unlike( $offset, qr/Jump to/,       'and no date jump, since these posts share a date' );
+    unlike( $offset, qr/Jump[ ]to/,     'and no date jump, since these posts share a date' );
 
 
     my $first = $render->( paginate_offset => 1, pages => 1, page => 1, pages_total => 3 );
     my $first = $render->( paginate_offset => 1, pages => 1, page => 1, pages_total => 3 );
     unlike( $first, qr/rel="prev"/, 'the first page offers no Prev' );
     unlike( $first, qr/rel="prev"/, 'the first page offers no Prev' );
@@ -478,13 +479,13 @@ subtest 'the paginator renders page links for a datasource' => sub {
 
 
     my $only = $render->( paginate_offset => 1, pages => 0, page => 1, pages_total => 1 );
     my $only = $render->( paginate_offset => 1, pages => 0, page => 1, pages_total => 1 );
     unlike( $only, qr/rel="prev"|rel="next"/, 'a single page offers neither' );
     unlike( $only, qr/rel="prev"|rel="next"/, 'a single page offers neither' );
-    like( $only, qr/id="paginator" class="disabled"/, 'and says so' );
+    like( $only, qr/id="paginator"[ ]class="disabled"/, 'and says so' );
 
 
     # Stored posts are untouched by any of this.
     # Stored posts are untouched by any of this.
     my $cursor = $render->( paginate_offset => 0, pages => 1, older => 1700, newer => 1800, like => 'x' );
     my $cursor = $render->( paginate_offset => 0, pages => 1, older => 1700, newer => 1800, like => 'x' );
     like( $cursor, qr/href="\?older=1700&limit=25/, 'the cursor paginator still pages by cursor' );
     like( $cursor, qr/href="\?older=1700&limit=25/, 'the cursor paginator still pages by cursor' );
     like( $cursor, qr/href="\?newer=1800&limit=25/, 'both ways' );
     like( $cursor, qr/href="\?newer=1800&limit=25/, 'both ways' );
-    like( $cursor, qr/Jump to/,                     'and keeps its date jump' );
+    like( $cursor, qr/Jump[ ]to/,                   'and keeps its date jump' );
     unlike( $cursor, qr/\?page=/, 'with no page numbers' );
     unlike( $cursor, qr/\?page=/, 'with no page numbers' );
 };
 };
 
 

+ 2 - 1
t/Trog-Log-DBI.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;
@@ -39,7 +40,7 @@ subtest 'a write the database refuses is a warning, not a die' => sub {
 
 
     is( $died, undef, 'the caller of the log does not die' );
     is( $died, undef, 'the caller of the log does not die' );
     is( $res,  undef, 'the write reports that it failed' );
     is( $res,  undef, 'the write reports that it failed' );
-    like( $stderr, qr/Could not record request r-2 in the metrics database: .*refused for the test/, 'and says why on stderr' );
+    like( $stderr, qr/Could[ ]not[ ]record[ ]request[ ]r-2[ ]in[ ]the[ ]metrics[ ]database:[ ]\N*refused[ ]for[ ]the[ ]test/, 'and says why on stderr' );
 };
 };
 
 
 done_testing();
 done_testing();

+ 8 - 7
t/Trog-Log-Syslog.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;
@@ -36,9 +37,9 @@ subtest 'a line arrives as the syslog of this machine reads it' => sub {
     my $got = received();
     my $got = received();
 
 
     # daemon is facility 3 and info is severity 6, so the priority is 3*8+6.
     # daemon is facility 3 and info is severity 6, so the priority is 3*8+6.
-    like( $got, qr/^<30>/,                                                                                             'as daemon.info' );
-    like( $got, qr/\btcms\[$$\]: /,                                                                                    'tagged with the program name and the pid' );
-    like( $got, qr/: 2026-09-29T12:00:00Z \[INFO\]: RequestId x From 192\.0\.2\.1 \|nobody\| Failed login for user\z/, 'with the line that the file gets, and no newline' );
+    like( $got, qr/^<30>/,                                                                                                                   'as daemon.info' );
+    like( $got, qr/\btcms\[$$\]:[ ]/,                                                                                                        'tagged with the program name and the pid' );
+    like( $got, qr/:[ ]2026-09-29T12:00:00Z[ ]\[INFO\]:[ ]RequestId[ ]x[ ]From[ ]192\.0\.2\.1[ ]\|nobody\|[ ]Failed[ ]login[ ]for[ ]user\z/, 'with the line that the file gets, and no newline' );
 };
 };
 
 
 subtest 'each level keeps its severity' => sub {
 subtest 'each level keeps its severity' => sub {
@@ -46,14 +47,14 @@ subtest 'each level keeps its severity' => sub {
     foreach my $level ( sort keys %severity ) {
     foreach my $level ( sort keys %severity ) {
         $log->log( level => $level, message => "at $level" );
         $log->log( level => $level, message => "at $level" );
         my $priority = 3 * 8 + $severity{$level};
         my $priority = 3 * 8 + $severity{$level};
-        like( received(), qr/^<$priority>.*: at $level$/, "$level is severity $severity{$level}" );
+        like( received(), qr/^<$priority>\N*:[ ]at[ ]$level$/, "$level is severity $severity{$level}" );
     }
     }
 };
 };
 
 
 subtest 'nothing below the level goes out' => sub {
 subtest 'nothing below the level goes out' => sub {
     $log->debug('too quiet');
     $log->debug('too quiet');
     $log->info('loud enough');
     $log->info('loud enough');
-    like( received(), qr/loud enough$/, 'the debug line was not sent' );
+    like( received(), qr/loud[ ]enough$/, 'the debug line was not sent' );
 };
 };
 
 
 subtest 'a syslog that went away does not take the application with it' => sub {
 subtest 'a syslog that went away does not take the application with it' => sub {
@@ -92,9 +93,9 @@ subtest 'log_init adds the loggers that tpsgi.ini names' => sub {
 
 
     no warnings 'once';
     no warnings 'once';
     is( $Test::Capture::given{log_dir}, $dir, 'handed the log directory, as tPSGI hands it' );
     is( $Test::Capture::given{log_dir}, $dir, 'handed the log directory, as tPSGI hands it' );
-    like( $Test::Capture::lines[-1], qr/through every output/, 'and given the lines' );
+    like( $Test::Capture::lines[-1], qr/through[ ]every[ ]output/, 'and given the lines' );
 
 
-    like( exception { Trog::Log::log_init( "$dir/tcms.log", 'info', ['../../etc/passwd'] ) }, qr/not a module name/, 'a logger that is not a module name is refused' );
+    like( exception { Trog::Log::log_init( "$dir/tcms.log", 'info', ['../../etc/passwd'] ) }, qr/not[ ]a[ ]module[ ]name/, 'a logger that is not a module name is refused' );
 };
 };
 
 
 Test::NoWarnings::had_no_warnings();
 Test::NoWarnings::had_no_warnings();

+ 1 - 0
t/Trog-Routes-HTML-config.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;

+ 1 - 0
t/Trog-Routes-HTML-post-wizard-browser.t

@@ -26,6 +26,7 @@
 # NODE_PATH.  A global install under nvm is not one of those places.
 # NODE_PATH.  A global install under nvm is not one of those places.
 
 
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use FindBin;
 use FindBin;

+ 1 - 0
t/Trog-Routes-HTML-series-lifecycle-sqlite.t

@@ -9,6 +9,7 @@
 # the point Trog::Data builds it.
 # the point Trog::Data builds it.
 
 
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;

+ 60 - 59
t/Trog-Routes-HTML-series-lifecycle.t

@@ -9,6 +9,7 @@
 # series() which delegates to it -- and matches on the returned HTML.
 # series() which delegates to it -- and matches on the returned HTML.
 
 
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 # FindBin MUST come first.  $FindBin::Bin is derived from $0, which prove hands
 # FindBin MUST come first.  $FindBin::Bin is derived from $0, which prove hands
@@ -114,7 +115,7 @@ BEGIN {
     # gets run against each of them -- the point being that a data model is only
     # gets run against each of them -- the point being that a data model is only
     # finished when the app cannot tell which one it is talking to.
     # finished when the app cannot tell which one it is talking to.
     my $stock = File::Slurper::read_text("$REPO/config/default.cfg");
     my $stock = File::Slurper::read_text("$REPO/config/default.cfg");
-    $stock =~ s/^(\s*data_model=).*$/$1$MODEL/m or die "could not set the data model in default.cfg";
+    $stock =~ s/^(\s*data_model=)\N*$/$1$MODEL/m or die "could not set the data model in default.cfg";
     File::Slurper::write_text( "$ROOT/config/default.cfg", $stock );
     File::Slurper::write_text( "$ROOT/config/default.cfg", $stock );
 
 
     # Hardcoded paths in Auth.pm, TagIndex.pm, the data models and Log.pm -- not negotiable.
     # Hardcoded paths in Auth.pm, TagIndex.pm, the data models and Log.pm -- not negotiable.
@@ -322,21 +323,21 @@ sub _data { return Trog::Data->new( Trog::Config::get() ) }
 # emits other class="topbar" anchors outside that span.
 # emits other class="topbar" anchors outside that span.
 sub _topbar_titles {
 sub _topbar_titles {
     my ($body) = @_;
     my ($body) = @_;
-    my ($span) = $body =~ m{<span id="categories">(.*?)</span>}s;
+    my ($span) = $body =~ m{<span[ ]id="categories">(.*?)</span>}s;
     return () unless defined $span;
     return () unless defined $span;
     return $span =~ m{class="topbar">([^<]*)</a>}g;
     return $span =~ m{class="topbar">([^<]*)</a>}g;
 }
 }
 
 
 # post_title.tx emits exactly one of these per displayed post.
 # post_title.tx emits exactly one of these per displayed post.
 sub _post_count {
 sub _post_count {
-    my $n = () = $_[0] =~ m{<h3 class='blogtitles'>}g;
+    my $n = () = $_[0] =~ m{<h3[ ]class='blogtitles'>}g;
     return $n;
     return $n;
 }
 }
 
 
 # The rendered page is ~40KB and mostly JavaScript.  Show the content well.
 # The rendered page is ~40KB and mostly JavaScript.  Show the content well.
 sub _kontent {
 sub _kontent {
     my ($body) = @_;
     my ($body) = @_;
-    my ($k)    = $body =~ m{<div id="kontent" class="kontained">(.*?)<div id="rightbar"}s;
+    my ($k)    = $body =~ m{<div[ ]id="kontent"[ ]class="kontained">(.*?)<div[ ]id="rightbar"}s;
     $k //= $body;
     $k //= $body;
     $k =~ s/<script\b.*?<\/script>//gs;
     $k =~ s/<script\b.*?<\/script>//gs;
     return length($k) > 2500 ? substr( $k, 0, 2500 ) . "\n...[truncated]" : $k;
     return length($k) > 2500 ? substr( $k, 0, 2500 ) . "\n...[truncated]" : $k;
@@ -462,7 +463,7 @@ subtest 'the post wizard invents a new post type' => sub {
     ok( -f 'www/templates/html/components/forms/spec_widget.json', 'the schema sidecar was written' );
     ok( -f 'www/templates/html/components/forms/spec_widget.json', 'the schema sidecar was written' );
 
 
     # The existing types are a dropdown now, not a list.
     # The existing types are a dropdown now, not a list.
-    like( $body, qr{<option value="spec_widget\.tx">}, 'and the new type is listed back to us' );
+    like( $body, qr{<option[ ]value="spec_widget\.tx">}, 'and the new type is listed back to us' );
 
 
     # This is the assertion that the sandbox is real: a symlinked template tree
     # This is the assertion that the sandbox is real: a symlinked template tree
     # would have put these in the developer's actual checkout.
     # would have put these in the developer's actual checkout.
@@ -470,9 +471,9 @@ subtest 'the post wizard invents a new post type' => sub {
 
 
     # The relation controls have to be present in every row and always submit,
     # The relation controls have to be present in every row and always submit,
     # or the parallel param_* arrays de-align server side.
     # or the parallel param_* arrays de-align server side.
-    like( $body, qr/name="param_relation_form"/, 'the relation target select is in the row template' );
-    like( $body, qr/name="param_relation_mode"/, 'so is the relation mode select' );
-    like( $body, qr/<option value="relation">/,  'and relation is offered as a field type' );
+    like( $body, qr/name="param_relation_form"/,  'the relation target select is in the row template' );
+    like( $body, qr/name="param_relation_mode"/,  'so is the relation mode select' );
+    like( $body, qr/<option[ ]value="relation">/, 'and relation is offered as a field type' );
 
 
     # A series can ask for its children to be tiled.  A generated type has to
     # A series can ask for its children to be tiled.  A generated type has to
     # carry that on its wrapper like the hand-written forms do, or it is the
     # carry that on its wrapper like the hand-written forms do, or it is the
@@ -551,7 +552,7 @@ subtest 'a datasource-backed type gets no editor' => sub {
         _admin( route => '/admin/wyzzerdd/save', method => 'POST', name => 'spec_bogus', datasource => 'Evil::Module' ),
         _admin( route => '/admin/wyzzerdd/save', method => 'POST', name => 'spec_bogus', datasource => 'Evil::Module' ),
         \&Trog::Routes::HTML::post_wizard_save,
         \&Trog::Routes::HTML::post_wizard_save,
     );
     );
-    like( $body, qr/not a datasource I can find/, 'a datasource we did not find is refused' );
+    like( $body, qr/not[ ]a[ ]datasource[ ]I[ ]can[ ]find/, 'a datasource we did not find is refused' );
     ok( !-e 'www/templates/html/components/forms/spec_bogus.tx', 'and nothing was written' );
     ok( !-e 'www/templates/html/components/forms/spec_bogus.tx', 'and nothing was written' );
 };
 };
 
 
@@ -619,18 +620,18 @@ subtest 'a datasource-backed series searches and paginates' => sub {
 
 
     my $titles = sub {
     my $titles = sub {
         my ($html) = @_;
         my ($html) = @_;
-        return [ $html =~ m{<div class="guest">(guest\d+) is}g ];
+        return [ $html =~ m{<div[ ]class="guest">(guest\d+)[ ]is}g ];
     };
     };
 
 
     my $first = $page_of->( limit => 5 );
     my $first = $page_of->( limit => 5 );
     is_deeply( $titles->($first), [qw{guest01 guest02 guest03 guest04 guest05}], 'the first page holds the first five, in a settled order' );
     is_deeply( $titles->($first), [qw{guest01 guest02 guest03 guest04 guest05}], 'the first page holds the first five, in a settled order' );
-    like( $first, qr/Page 1 of 3/,            'and says which page it is' );
+    like( $first, qr/Page[ ]1[ ]of[ ]3/,      'and says which page it is' );
     like( $first, qr{href="\?page=2&limit=5}, 'offering the next one' );
     like( $first, qr{href="\?page=2&limit=5}, 'offering the next one' );
     unlike( $first, qr/rel="prev"/, 'and no previous one' );
     unlike( $first, qr/rel="prev"/, 'and no previous one' );
 
 
     my $second = $page_of->( limit => 5, page => 2 );
     my $second = $page_of->( limit => 5, page => 2 );
     is_deeply( $titles->($second), [qw{guest06 guest07 guest08 guest09 guest10}], 'the second page carries on where the first stopped' );
     is_deeply( $titles->($second), [qw{guest06 guest07 guest08 guest09 guest10}], 'the second page carries on where the first stopped' );
-    like( $second, qr/Page 2 of 3/, 'and says so' );
+    like( $second, qr/Page[ ]2[ ]of[ ]3/, 'and says so' );
 
 
     my $third = $page_of->( limit => 5, page => 3 );
     my $third = $page_of->( limit => 5, page => 3 );
     is_deeply( $titles->($third), [qw{guest11 guest12}], 'the last page holds the remainder' );
     is_deeply( $titles->($third), [qw{guest11 guest12}], 'the last page holds the remainder' );
@@ -659,8 +660,8 @@ subtest 'a datasource-backed series searches and paginates' => sub {
     # And the two compose, which is what a reader paging through a search does.
     # And the two compose, which is what a reader paging through a search does.
     my $searched_page = $page_of->( limit => 4, like => 'shut', page => 2 );
     my $searched_page = $page_of->( limit => 4, like => 'shut', page => 2 );
     is_deeply( $titles->($searched_page), [qw{guest10 guest12}], 'paging through a search stays inside it' );
     is_deeply( $titles->($searched_page), [qw{guest10 guest12}], 'paging through a search stays inside it' );
-    like( $searched_page, qr/Page 2 of 2/, 'and pages against the matches, not everything' );
-    like( $searched_page, qr/like=shut/,   'carrying the search into the links' );
+    like( $searched_page, qr/Page[ ]2[ ]of[ ]2/, 'and pages against the matches, not everything' );
+    like( $searched_page, qr/like=shut/,         'carrying the search into the links' );
 };
 };
 
 
 subtest 'an ordinary series survives a page number in its URL' => sub {
 subtest 'an ordinary series survives a page number in its URL' => sub {
@@ -688,14 +689,14 @@ subtest 'an ordinary series survives a page number in its URL' => sub {
     # One child a page, so page two exists and holds the other one.
     # One child a page, so page two exists and holds the other one.
     my ( $code, $body, $err ) = _render( _anon( route => '/specpaged', limit => 1, page => 2 ), \&Trog::Routes::HTML::series );
     my ( $code, $body, $err ) = _render( _anon( route => '/specpaged', limit => 1, page => 2 ), \&Trog::Routes::HTML::series );
     is( $code, 200, 'a series page with ?page=2 still finds its series' ) or diag($err);
     is( $code, 200, 'a series page with ?page=2 still finds its series' ) or diag($err);
-    like( $body, qr/Spec Paged Series/, 'and is the right one' );
+    like( $body, qr/Spec[ ]Paged[ ]Series/, 'and is the right one' );
 
 
     ( $code, $body, $err ) = _render( _anon( route => '/specpaged', limit => 1, page => 1 ), \&Trog::Routes::HTML::series );
     ( $code, $body, $err ) = _render( _anon( route => '/specpaged', limit => 1, page => 1 ), \&Trog::Routes::HTML::series );
     is( $code, 200, 'as does page one' ) or diag($err);
     is( $code, 200, 'as does page one' ) or diag($err);
 
 
     # A stored series still pages by cursor, not by number: nothing about this
     # A stored series still pages by cursor, not by number: nothing about this
     # fix moves it onto the datasource paginator.
     # fix moves it onto the datasource paginator.
-    unlike( $body, qr/Page 1 of/, 'and it still pages by cursor rather than by number' );
+    unlike( $body, qr/Page[ ]1[ ]of/, 'and it still pages by cursor rather than by number' );
 };
 };
 
 
 subtest 'a directory index series' => sub {
 subtest 'a directory index series' => sub {
@@ -737,13 +738,13 @@ subtest 'a directory index series' => sub {
 
 
     my $names = sub {
     my $names = sub {
         my ($html) = @_;
         my ($html) = @_;
-        return [ $html =~ m{<div class="entry">(file\d+\.txt) \(}g ];
+        return [ $html =~ m{<div[ ]class="entry">(file\d+\.txt)[ ]\(}g ];
     };
     };
 
 
     ( $code, $body, $err ) = _render( _anon( route => '/specfiles' ), \&Trog::Routes::HTML::series );
     ( $code, $body, $err ) = _render( _anon( route => '/specfiles' ), \&Trog::Routes::HTML::series );
     is( $code, 200, 'the listing renders' ) or diag($err);
     is( $code, 200, 'the listing renders' ) or diag($err);
     is_deeply( $names->($body), [ map { "file$_.txt" } 1 .. 7 ], 'listing the directory, in name order' );
     is_deeply( $names->($body), [ map { "file$_.txt" } 1 .. 7 ], 'listing the directory, in name order' );
-    like( $body, qr/\(10 B\)/, 'with each entry\'s size' );
+    like( $body, qr/\(10[ ]B\)/, 'with each entry\'s size' );
 
 
     # post_title.tx quotes with single quotes; the point is the target, not the quoting.
     # post_title.tx quotes with single quotes; the point is the target, not the quoting.
     like( $body, qr{href=['"]/assets/spec_downloads/file1\.txt['"]}, 'and a link to the file itself' );
     like( $body, qr{href=['"]/assets/spec_downloads/file1\.txt['"]}, 'and a link to the file itself' );
@@ -752,7 +753,7 @@ subtest 'a directory index series' => sub {
     ( $code, $body, $err ) = _render( _anon( route => '/specfiles', limit => 3, page => 2 ), \&Trog::Routes::HTML::series );
     ( $code, $body, $err ) = _render( _anon( route => '/specfiles', limit => 3, page => 2 ), \&Trog::Routes::HTML::series );
     is( $code, 200, 'page two renders' ) or diag($err);
     is( $code, 200, 'page two renders' ) or diag($err);
     is_deeply( $names->($body), [qw{file4.txt file5.txt file6.txt}], 'holding the next three' );
     is_deeply( $names->($body), [qw{file4.txt file5.txt file6.txt}], 'holding the next three' );
-    like( $body, qr/Page 2 of 3/, 'and saying so' );
+    like( $body, qr/Page[ ]2[ ]of[ ]3/, 'and saying so' );
 
 
     # And searches like one.
     # And searches like one.
     ( $code, $body, $err ) = _render( _anon( route => '/specfiles', limit => 25, like => 'file3' ), \&Trog::Routes::HTML::series );
     ( $code, $body, $err ) = _render( _anon( route => '/specfiles', limit => 25, like => 'file3' ), \&Trog::Routes::HTML::series );
@@ -818,10 +819,10 @@ subtest 'the wizard offers the types that exist, and can reopen one' => sub {
     ( $code, $body, $err ) = _render( _admin( route => '/admin/wyzzerdd' ), \&Trog::Routes::HTML::post_wizard );
     ( $code, $body, $err ) = _render( _admin( route => '/admin/wyzzerdd' ), \&Trog::Routes::HTML::post_wizard );
     is( $code, 200, 'the wizard renders' ) or diag($err);
     is( $code, 200, 'the wizard renders' ) or diag($err);
 
 
-    like( $body, qr/id="wizard-existing"/,             'the existing types are a dropdown' );
-    like( $body, qr/<option value="">A new post type/, 'defaulting to a new one' );
-    like( $body, qr/<option value="spec_reopen\.tx">/, 'and offering the one we just made' );
-    like( $body, qr/id="wizard-description-editor"/,   'with a collapsed editor for the description' );
+    like( $body, qr/id="wizard-existing"/,                     'the existing types are a dropdown' );
+    like( $body, qr/<option[ ]value="">A[ ]new[ ]post[ ]type/, 'defaulting to a new one' );
+    like( $body, qr/<option[ ]value="spec_reopen\.tx">/,       'and offering the one we just made' );
+    like( $body, qr/id="wizard-description-editor"/,           'with a collapsed editor for the description' );
 
 
     my ($json) = $body =~ m{id="wizard-types"[^>]*>(.*?)</script>}s;
     my ($json) = $body =~ m{id="wizard-types"[^>]*>(.*?)</script>}s;
     ok( $json, 'the page carries what each type was made from' ) or return;
     ok( $json, 'the page carries what each type was made from' ) or return;
@@ -970,14 +971,14 @@ subtest 'a field can be declared editors-only' => sub {
     # An editor sees both.
     # An editor sees both.
     my ( $acode, $abody ) = _render( _admin( route => '/specsecretive' ), \&Trog::Routes::HTML::series );
     my ( $acode, $abody ) = _render( _admin( route => '/specsecretive' ), \&Trog::Routes::HTML::series );
     is( $acode, 200, 'the series renders for an admin' );
     is( $acode, 200, 'the series renders for an admin' );
-    like( $abody, qr{<div class="h">private value</div>}, 'who sees the private field' );
+    like( $abody, qr{<div[ ]class="h">private[ ]value</div>}, 'who sees the private field' );
 
 
     # A logged out reader sees only the public one, without the template
     # A logged out reader sees only the public one, without the template
     # having guarded anything.
     # having guarded anything.
     my ( $ncode, $nbody ) = _render( _anon( route => '/specsecretive' ), \&Trog::Routes::HTML::series );
     my ( $ncode, $nbody ) = _render( _anon( route => '/specsecretive' ), \&Trog::Routes::HTML::series );
     is( $ncode, 200, 'and for everyone else' );
     is( $ncode, 200, 'and for everyone else' );
-    like( $nbody, qr{<div class="s">public value</div>}, 'who still sees the public field' );
-    unlike( $nbody, qr/private value/, 'but not the private one' );
+    like( $nbody, qr{<div[ ]class="s">public[ ]value</div>}, 'who still sees the public field' );
+    unlike( $nbody, qr/private[ ]value/, 'but not the private one' );
 };
 };
 
 
 #--------------------------------------------------------------------------
 #--------------------------------------------------------------------------
@@ -1082,12 +1083,12 @@ subtest 'entities' => sub {
     my $body = _series_page( 'entities', 'specentities', $title, 2 ) or return;
     my $body = _series_page( 'entities', 'specentities', $title, 2 ) or return;
 
 
     like( $body, qr{id="postData-\Q$PAYEE->{id}\E"}, 'payee body rendered' );
     like( $body, qr{id="postData-\Q$PAYEE->{id}\E"}, 'payee body rendered' );
-    like( $body, qr/Acme Payments LLC/,              'payee data rendered' );
-    like( $body, qr/Wile E Coyote/,                  'payor data rendered' );
+    like( $body, qr/Acme[ ]Payments[ ]LLC/,          'payee data rendered' );
+    like( $body, qr/Wile[ ]E[ ]Coyote/,              'payor data rendered' );
 
 
     # entity_details.tx is included only in the editor half, so these are stored
     # entity_details.tx is included only in the editor half, so these are stored
     # but never displayed.  Pinning that so a template change is noticed.
     # but never displayed.  Pinning that so a template change is noticed.
-    unlike( $body, qr/Carrier Pigeon/, 'payment details are not shown to anonymous readers' );
+    unlike( $body, qr/Carrier[ ]Pigeon/, 'payment details are not shown to anonymous readers' );
     is( _find_post('Spec Payee Entity')->{payment_method}, 'ACH', 'but payment_method did survive the schema' );
     is( _find_post('Spec Payee Entity')->{payment_method}, 'ACH', 'but payment_method did survive the schema' );
 };
 };
 
 
@@ -1107,11 +1108,11 @@ subtest 'blog' => sub {
 
 
     my $body = _series_page( 'blog', 'specblog', $title, 1 ) or return;
     my $body = _series_page( 'blog', 'specblog', $title, 1 ) or return;
 
 
-    like( $body, qr/Spec Blog Child/,                'child title rendered' );
+    like( $body, qr/Spec[ ]Blog[ ]Child/,            'child title rendered' );
     like( $body, qr{href='/posts/\Q$child->{id}\E'}, 'child permalink rendered' );
     like( $body, qr{href='/posts/\Q$child->{id}\E'}, 'child permalink rendered' );
     like( $body, qr{id="postData-\Q$child->{id}\E"}, 'child body block rendered' );
     like( $body, qr{id="postData-\Q$child->{id}\E"}, 'child body block rendered' );
-    like( $body, qr/Spec blog body text\./,          'child body text rendered' );
-    unlike( $body, qr/Spec Payee Entity/, 'no bleed from the entities series' );
+    like( $body, qr/Spec[ ]blog[ ]body[ ]text\./,    'child body text rendered' );
+    unlike( $body, qr/Spec[ ]Payee[ ]Entity/, 'no bleed from the entities series' );
 };
 };
 
 
 subtest 'microblog' => sub {
 subtest 'microblog' => sub {
@@ -1131,9 +1132,9 @@ subtest 'microblog' => sub {
 
 
     my $body = _series_page( 'microblog', 'specmicroblog', $title, 1 ) or return;
     my $body = _series_page( 'microblog', 'specmicroblog', $title, 1 ) or return;
 
 
-    like( $body, qr{href='https://example\.com/spec' >Spec Microblog Child</a>}, 'title links out to href' );
-    like( $body, qr{id="postData-\Q$child->{id}\E"},                             'body block rendered' );
-    like( $body, qr/Spec microblog body\./,                                      'body text rendered' );
+    like( $body, qr{href='https://example\.com/spec'[ ]>Spec[ ]Microblog[ ]Child</a>}, 'title links out to href' );
+    like( $body, qr{id="postData-\Q$child->{id}\E"},                                   'body block rendered' );
+    like( $body, qr/Spec[ ]microblog[ ]body\./,                                        'body text rendered' );
 };
 };
 
 
 subtest 'file' => sub {
 subtest 'file' => sub {
@@ -1164,12 +1165,12 @@ subtest 'file' => sub {
 
 
     like( $body, qr{href='/assets/\Q$child->{id}\E\.spec-upload\.txt'}, 'title links to the asset' );
     like( $body, qr{href='/assets/\Q$child->{id}\E\.spec-upload\.txt'}, 'title links to the asset' );
     like( $body, qr{id="postData-\Q$child->{id}\E"},                    'body block carries a unique id, like every other form' );
     like( $body, qr{id="postData-\Q$child->{id}\E"},                    'body block carries a unique id, like every other form' );
-    like( $body, qr/Spec file body\./,                                  'body text rendered' );
+    like( $body, qr/Spec[ ]file[ ]body\./,                              'body text rendered' );
 
 
     # This form used to emit <div class="postData" id="postData" class="responsive-text">
     # This form used to emit <div class="postData" id="postData" class="responsive-text">
     # -- a duplicated class attribute, and an id shared by every file post on
     # -- a duplicated class attribute, and an id shared by every file post on
     # the page.  Neither should come back.
     # the page.  Neither should come back.
-    unlike( $body, qr{<div class="postData" id="postData"}, 'no duplicated class attribute' );
+    unlike( $body, qr{<div[ ]class="postData"[ ]id="postData"}, 'no duplicated class attribute' );
 
 
     # text/plain, so none of the media branches fire.
     # text/plain, so none of the media branches fire.
     unlike( $body, qr/<video/, 'no video block for a text file' );
     unlike( $body, qr/<video/, 'no video block for a text file' );
@@ -1196,8 +1197,8 @@ subtest 'presentation' => sub {
     my $body = _series_page( 'presentation', 'specpresentation', $title, 1 ) or return;
     my $body = _series_page( 'presentation', 'specpresentation', $title, 1 ) or return;
 
 
     # Slides only render under nochrome; the series page just links to the deck.
     # Slides only render under nochrome; the series page just links to the deck.
-    like( $body, qr{<a href="/posts/\Q$child->{id}\E\?embed=1&nochrome=1">Click Here to view</a>}, 'deck link rendered' );
-    unlike( $body, qr/Slide one body\./, 'slides themselves are not rendered inline' );
+    like( $body, qr{<a[ ]href="/posts/\Q$child->{id}\E\?embed=1&nochrome=1">Click[ ]Here[ ]to[ ]view</a>}, 'deck link rendered' );
+    unlike( $body, qr/Slide[ ]one[ ]body\./, 'slides themselves are not rendered inline' );
 };
 };
 
 
 subtest 'invoice' => sub {
 subtest 'invoice' => sub {
@@ -1228,19 +1229,19 @@ subtest 'invoice' => sub {
 
 
     my $body = _series_page( 'invoice', 'specinvoice', $title, 1 ) or return;
     my $body = _series_page( 'invoice', 'specinvoice', $title, 1 ) or return;
 
 
-    like( $body, qr/Invoice ID# \Q$child->{id}\E/, 'invoice header rendered' );
+    like( $body, qr/Invoice[ ]ID[#][ ]\Q$child->{id}\E/, 'invoice header rendered' );
 
 
     # These two only resolve because the entities subtest ran first and left
     # These two only resolve because the entities subtest ran first and left
     # public entities.tx posts behind for the enrich hook to find.
     # public entities.tx posts behind for the enrich hook to find.
-    like( $body, qr/Acme Payments LLC/, 'payee entity resolved' );
-    like( $body, qr/Wile E Coyote/,     'payor entity resolved' );
-    like( $body, qr/ACH/,               'payee payment method pulled through' );
+    like( $body, qr/Acme[ ]Payments[ ]LLC/, 'payee entity resolved' );
+    like( $body, qr/Wile[ ]E[ ]Coyote/,     'payor entity resolved' );
+    like( $body, qr/ACH/,                   'payee payment method pulled through' );
 
 
-    like( $body, qr/Widget assembly/, 'line items rendered' );
-    like( $body, qr/Total: \$500/,    'total summed from the line item prices' );
+    like( $body, qr/Widget[ ]assembly/, 'line items rendered' );
+    like( $body, qr/Total:[ ]\$500/,    'total summed from the line item prices' );
 
 
     my $due = $child->{modified} + 30 * 86400;
     my $due = $child->{modified} + 30 * 86400;
-    like( $body, qr{Due: <span class="unixtimestamp">\Q$due\E</span>}, 'due date counts from the latest edit, not the first save' );
+    like( $body, qr{Due:[ ]<span[ ]class="unixtimestamp">\Q$due\E</span>}, 'due date counts from the latest edit, not the first save' );
 };
 };
 
 
 subtest 'a private post is not served to a visitor who asks for it by id' => sub {
 subtest 'a private post is not served to a visitor who asks for it by id' => sub {
@@ -1255,11 +1256,11 @@ subtest 'a private post is not served to a visitor who asks for it by id' => sub
 
 
     my ( $code, $body ) = _render( _anon( route => "/posts/$secret->{id}" ), \&Trog::Routes::HTML::posts );
     my ( $code, $body ) = _render( _anon( route => "/posts/$secret->{id}" ), \&Trog::Routes::HTML::posts );
     is( $code, 404, 'a visitor gets a 404' );
     is( $code, 404, 'a visitor gets a 404' );
-    unlike( $body, qr/Spec private body/, 'and none of the post' );
+    unlike( $body, qr/Spec[ ]private[ ]body/, 'and none of the post' );
 
 
     ( $code, $body ) = _render( _admin( route => "/posts/$secret->{id}" ), \&Trog::Routes::HTML::posts );
     ( $code, $body ) = _render( _admin( route => "/posts/$secret->{id}" ), \&Trog::Routes::HTML::posts );
     is( $code, 200, 'an admin gets the post' );
     is( $code, 200, 'an admin gets the post' );
-    like( $body, qr/Spec private body/, 'with its body' );
+    like( $body, qr/Spec[ ]private[ ]body/, 'with its body' );
 };
 };
 
 
 subtest 'spec_widget (the wizard-built type)' => sub {
 subtest 'spec_widget (the wizard-built type)' => sub {
@@ -1286,8 +1287,8 @@ subtest 'spec_widget (the wizard-built type)' => sub {
     # The payoff: this can only render if the wizard wrote spec_widget.json,
     # The payoff: this can only render if the wizard wrote spec_widget.json,
     # schema_for merged it over the base post schema, and validate() therefore
     # schema_for merged it over the base post schema, and validate() therefore
     # kept 'flavor' instead of deleting it as an unknown key.
     # kept 'flavor' instead of deleting it as an unknown key.
-    like( $body, qr{<span class="spec-flavor">Strawberry</span>}, 'the custom field rendered on the page' );
-    like( $body, qr/Spec widget body\./,                          'and the body came through the generated template' );
+    like( $body, qr{<span[ ]class="spec-flavor">Strawberry</span>}, 'the custom field rendered on the page' );
+    like( $body, qr/Spec[ ]widget[ ]body\./,                        'and the body came through the generated template' );
 };
 };
 
 
 subtest 'a wizard-built type can depend on another type' => sub {
 subtest 'a wizard-built type can depend on another type' => sub {
@@ -1391,8 +1392,8 @@ subtest 'a wizard-built type can depend on another type' => sub {
     my $page = _series_page( 'spec_relator', 'specrelator', 'Spec Relator Series', 1 ) or return;
     my $page = _series_page( 'spec_relator', 'specrelator', 'Spec Relator Series', 1 ) or return;
 
 
     # Both halves of the relation resolved: the one we picked, and the set.
     # Both halves of the relation resolved: the one we picked, and the set.
-    like( $page, qr{<span class="chosen">Spec Target Post</span>}, 'the picked post resolved into the page' );
-    like( $page, qr{<span class="each">Spec Target Post</span>},   'and so did the whole set' );
+    like( $page, qr{<span[ ]class="chosen">Spec[ ]Target[ ]Post</span>}, 'the picked post resolved into the page' );
+    like( $page, qr{<span[ ]class="each">Spec[ ]Target[ ]Post</span>},   'and so did the whole set' );
 };
 };
 
 
 subtest '/api/posts_of_form feeds the relation picker' => sub {
 subtest '/api/posts_of_form feeds the relation picker' => sub {
@@ -1490,8 +1491,8 @@ subtest 'saves report back through the jsalert banner' => sub {
         \&Trog::Routes::HTML::series,
         \&Trog::Routes::HTML::series,
     );
     );
     is( $rcode, 200, 'the destination renders' ) or diag($err);
     is( $rcode, 200, 'the destination renders' ) or diag($err);
-    like( $body, qr/var loginFailure = 0;/,                      'the banner is in success mode' );
-    like( $body, qr/Saved post &#39;Spec Feedback Child&#39;\./, 'and says what was saved' );
+    like( $body, qr/var[ ]loginFailure[ ]=[ ]0;/,                            'the banner is in success mode' );
+    like( $body, qr/Saved[ ]post[ ]&[#]39;Spec[ ]Feedback[ ]Child&[#]39;\./, 'and says what was saved' );
 
 
     # Nothing to navigate on to, so the banner must not bounce us anywhere.
     # Nothing to navigate on to, so the banner must not bounce us anywhere.
     unlike( $body, qr/window\.location=/, 'and does not redirect onwards' );
     unlike( $body, qr/window\.location=/, 'and does not redirect onwards' );
@@ -1512,8 +1513,8 @@ subtest 'saves report back through the jsalert banner' => sub {
     %h = @{ $headers || [] };
     %h = @{ $headers || [] };
     is( $code,        303,                'a rejected save redirects too, rather than dead-ending on a 400' );
     is( $code,        303,                'a rejected save redirects too, rather than dead-ending on a 400' );
     is( $h{Location}, '/secure/specblog', 'to the same place a good one goes' );
     is( $h{Location}, '/secure/specblog', 'to the same place a good one goes' );
-    like( $h{'Set-Cookie'},                              qr/^tcmsfeedback=1:/, 'flagged as failed' );
-    like( URI::Escape::uri_unescape( $h{'Set-Cookie'} ), qr/Not in enum list/, 'carrying the validator error' );
+    like( $h{'Set-Cookie'},                              qr/^tcmsfeedback=1:/,       'flagged as failed' );
+    like( URI::Escape::uri_unescape( $h{'Set-Cookie'} ), qr/Not[ ]in[ ]enum[ ]list/, 'carrying the validator error' );
 
 
     ok( !_find_post('Spec Rejected Child'), 'and the bad post was not written' );
     ok( !_find_post('Spec Rejected Child'), 'and the bad post was not written' );
 
 
@@ -1524,8 +1525,8 @@ subtest 'saves report back through the jsalert banner' => sub {
         \&Trog::Routes::HTML::series,
         \&Trog::Routes::HTML::series,
     );
     );
     is( $rcode, 200, 'the destination still renders' ) or diag($err);
     is( $rcode, 200, 'the destination still renders' ) or diag($err);
-    like( $body, qr/var loginFailure = 1;/, 'the banner is in failure mode' );
-    like( $body, qr/Not in enum list/,      'and shows why' );
+    like( $body, qr/var[ ]loginFailure[ ]=[ ]1;/, 'the banner is in failure mode' );
+    like( $body, qr/Not[ ]in[ ]enum[ ]list/,      'and shows why' );
 
 
     # Read once: the cookie is expired by the page that rendered it, or the
     # Read once: the cookie is expired by the page that rendered it, or the
     # banner follows the reader around until it times out on its own.
     # banner follows the reader around until it times out on its own.
@@ -1534,7 +1535,7 @@ subtest 'saves report back through the jsalert banner' => sub {
         \&Trog::Routes::HTML::series,
         \&Trog::Routes::HTML::series,
     );
     );
     my %rh = @{ $rheaders || [] };
     my %rh = @{ $rheaders || [] };
-    like( $rh{'Set-Cookie'}, qr/^tcmsfeedback=;.*Max-Age=0/, 'and the page that shows it expires it' );
+    like( $rh{'Set-Cookie'}, qr/^tcmsfeedback=;\N*Max-Age=0/, 'and the page that shows it expires it' );
 
 
     # A page showing a banner must not be cached, or the one person who saved
     # A page showing a banner must not be cached, or the one person who saved
     # something has their message served to everybody.  The query parameter
     # something has their message served to everybody.  The query parameter

+ 2 - 1
t/Trog-SQLite-TagIndex.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;
@@ -94,7 +95,7 @@ subtest 'rebuild is one transaction' => sub {
 
 
     my $broken = Broken::Data->new( post() );
     my $broken = Broken::Data->new( post() );
     ok( !eval { Trog::SQLite::TagIndex::rebuild($broken); 1 }, 'a rebuild that fails part way dies' );
     ok( !eval { Trog::SQLite::TagIndex::rebuild($broken); 1 }, 'a rebuild that fails part way dies' );
-    like( $@, qr/fell over/a, 'with the error that stopped it' );
+    like( $@, qr/fell[ ]over/, 'with the error that stopped it' );
     is_deeply( index_rows(), $before, 'and leaves the index as it was' );
     is_deeply( index_rows(), $before, 'and leaves the index as it was' );
 };
 };
 
 

+ 1 - 0
t/Trog-Themes-themed-templates.t

@@ -1,4 +1,5 @@
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use Test::More;
 use Test::More;

+ 7 - 6
t/Trog-Vault.t

@@ -5,6 +5,7 @@
 # on its own is not enough, and that a code is worth one thing.
 # on its own is not enough, and that a code is worth one thing.
 
 
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use FindBin;
 use FindBin;
@@ -70,7 +71,7 @@ subtest 'what a secret looks like on disk' => sub {
     is( scalar(@$rows), 1, 'and lands in one row' );
     is( scalar(@$rows), 1, 'and lands in one row' );
 
 
     my $row = $rows->[0];
     my $row = $rows->[0];
-    unlike( $row->{ciphertext}, qr/correct horse/, 'which does not contain the secret' );
+    unlike( $row->{ciphertext}, qr/correct[ ]horse/, 'which does not contain the secret' );
     is( length( $row->{salt} ), 32,                    'carries a fresh 32 byte salt' );
     is( length( $row->{salt} ), 32,                    'carries a fresh 32 byte salt' );
     is( length( $row->{tag} ),  16,                    'and a GCM tag' );
     is( length( $row->{tag} ),  16,                    'and a GCM tag' );
     is( $row->{key_id},         Trog::Vault::key_id(), 'naming the key it was sealed under' );
     is( $row->{key_id},         Trog::Vault::key_id(), 'naming the key it was sealed under' );
@@ -160,7 +161,7 @@ subtest 'no key, no vault' => sub {
     ok( !Trog::Vault::has_key(), 'an installation with no key has no vault' );
     ok( !Trog::Vault::has_key(), 'an installation with no key has no vault' );
     my ( $ok, $why ) = Trog::Vault::set( 'bob', 'nowhere', 'to put it' );
     my ( $ok, $why ) = Trog::Vault::set( 'bob', 'nowhere', 'to put it' );
     is( $ok, 0, 'storing is refused' );
     is( $ok, 0, 'storing is refused' );
-    like( $why, qr/no vault key/, 'saying why, since this is a thing to go and configure' );
+    like( $why, qr/no[ ]vault[ ]key/, 'saying why, since this is a thing to go and configure' );
     is( Trog::Vault::get( 'bob', 'keepass' ), undef, 'and nothing already stored is offered' );
     is( Trog::Vault::get( 'bob', 'keepass' ), undef, 'and nothing already stored is offered' );
 };
 };
 
 
@@ -221,7 +222,7 @@ subtest 'a code is worth one thing' => sub {
 
 
     ( $ok, $why ) = Trog::Auth::spend_totp( 'bob', $code );
     ( $ok, $why ) = Trog::Auth::spend_totp( 'bob', $code );
     is( $ok, 0, 'and is not accepted a second time' );
     is( $ok, 0, 'and is not accepted a second time' );
-    like( $why, qr/already been used/, 'saying so, since the answer is to wait thirty seconds' );
+    like( $why, qr/already[ ]been[ ]used/, 'saying so, since the answer is to wait thirty seconds' );
 
 
     # A code from before the one just spent is also spent, whatever the
     # A code from before the one just spent is also spent, whatever the
     # arithmetic says: that is what walking the counter forward is for.
     # arithmetic says: that is what walking the counter forward is for.
@@ -236,7 +237,7 @@ subtest 'a code is worth one thing' => sub {
 
 
     ( $ok, $why ) = Trog::Auth::spend_totp( 'eve', $totp->expected_totp_code( $now + 30 ) );
     ( $ok, $why ) = Trog::Auth::spend_totp( 'eve', $totp->expected_totp_code( $now + 30 ) );
     is( $ok, 0, 'somebody who has not enrolled cannot spend anything' );
     is( $ok, 0, 'somebody who has not enrolled cannot spend anything' );
-    like( $why, qr/no second factor/, 'and is told that rather than that their code was wrong' );
+    like( $why, qr/no[ ]second[ ]factor/, 'and is told that rather than that their code was wrong' );
 
 
     # The next window is a new code and works, which is the whole cost of this.
     # The next window is a new code and works, which is the whole cost of this.
     ( $ok, $why ) = Trog::Auth::spend_totp( 'bob', $totp->expected_totp_code( $now + 30 ) );
     ( $ok, $why ) = Trog::Auth::spend_totp( 'bob', $totp->expected_totp_code( $now + 30 ) );
@@ -344,7 +345,7 @@ subtest 'when the key is gone' => sub {
         my ( $stored, $why ) = Trog::Vault::set( 'bob', 'replacement', 'a new one' );
         my ( $stored, $why ) = Trog::Vault::set( 'bob', 'replacement', 'a new one' );
         if ( $what eq 'no key at all' ) {
         if ( $what eq 'no key at all' ) {
             is( $stored, 0, "$what: storing is refused rather than silently lost" );
             is( $stored, 0, "$what: storing is refused rather than silently lost" );
-            like( $why, qr/no vault key/, "$what: saying why" );
+            like( $why, qr/no[ ]vault[ ]key/, "$what: saying why" );
         }
         }
         else {
         else {
             is( $stored,                                  1,           "$what: a new key stores new secrets fine" );
             is( $stored,                                  1,           "$what: a new key stores new secrets fine" );
@@ -422,7 +423,7 @@ subtest 'a write the database refuses is an answer, not a crash' => sub {
     $dbh->do("DROP TRIGGER temp.refuse_spend");
     $dbh->do("DROP TRIGGER temp.refuse_spend");
 
 
     is( $ok, 0, 'a right code that cannot be recorded as spent is refused' );
     is( $ok, 0, 'a right code that cannot be recorded as spent is refused' );
-    like( $why, qr/could not record the code/, 'saying why' );
+    like( $why, qr/could[ ]not[ ]record[ ]the[ ]code/, 'saying why' );
 };
 };
 
 
 done_testing();
 done_testing();

+ 9 - 8
t/bin-migrate.t

@@ -6,6 +6,7 @@
 # model, migrates, and compares the two models query for query.
 # model, migrates, and compares the two models query for query.
 
 
 use 5.040;
 use 5.040;
+use re '/aasx';
 use warnings;
 use warnings;
 
 
 use FindBin;
 use FindBin;
@@ -85,8 +86,8 @@ sub post {
 subtest 'nothing to migrate' => sub {
 subtest 'nothing to migrate' => sub {
     my ( $rc, $out ) = migrate();
     my ( $rc, $out ) = migrate();
     isnt( $rc, 0, 'an empty datastore is an error rather than a silent success' );
     isnt( $rc, 0, 'an empty datastore is an error rather than a silent success' );
-    like( $out, qr/No posts found/, 'and says so' );
-    unlike( $out, qr/ at \S+ line \d+/, 'without a stack trace, since it is not a crash' );
+    like( $out, qr/No[ ]posts[ ]found/, 'and says so' );
+    unlike( $out, qr/[ ]at[ ]\S+[ ]line[ ]\d+/, 'without a stack trace, since it is not a crash' );
 };
 };
 
 
 # A corpus with the awkward bits: several versions of one post, a private one,
 # A corpus with the awkward bits: several versions of one post, a private one,
@@ -117,8 +118,8 @@ reset_flatfile_cache();
 subtest 'a dry run writes nothing' => sub {
 subtest 'a dry run writes nothing' => sub {
     my ( $rc, $out ) = migrate('--dry-run');
     my ( $rc, $out ) = migrate('--dry-run');
     is( $rc, 0, 'it succeeds' ) or diag($out);
     is( $rc, 0, 'it succeeds' ) or diag($out);
-    like( $out, qr/Would carry over 6 post version\(s\)/, 'and counts every version, not every post' );
-    like( $out, qr/Nothing was written/,                  'and says it wrote nothing' );
+    like( $out, qr/Would[ ]carry[ ]over[ ]6[ ]post[ ]version\(s\)/, 'and counts every version, not every post' );
+    like( $out, qr/Nothing[ ]was[ ]written/,                        'and says it wrote nothing' );
 
 
     is( $sqlite->count(), 0, 'because it wrote nothing' );
     is( $sqlite->count(), 0, 'because it wrote nothing' );
 };
 };
@@ -126,8 +127,8 @@ subtest 'a dry run writes nothing' => sub {
 subtest 'the migration itself' => sub {
 subtest 'the migration itself' => sub {
     my ( $rc, $out ) = migrate();
     my ( $rc, $out ) = migrate();
     is( $rc, 0, 'it succeeds' ) or diag($out);
     is( $rc, 0, 'it succeeds' ) or diag($out);
-    like( $out, qr/Carried over 6 post version\(s\), skipped 0/,                            'carrying over every version' );
-    like( $out, qr/data\/files holds 6 post version\(s\); data\/posts\.sqlite now holds 6/, 'and says what actually landed' );
+    like( $out, qr/Carried[ ]over[ ]6[ ]post[ ]version\(s\),[ ]skipped[ ]0/,                                'carrying over every version' );
+    like( $out, qr/data\/files[ ]holds[ ]6[ ]post[ ]version\(s\);[ ]data\/posts\.sqlite[ ]now[ ]holds[ ]6/, 'and says what actually landed' );
 
 
     is( $sqlite->count(), 4, 'four posts' );
     is( $sqlite->count(), 4, 'four posts' );
 
 
@@ -170,7 +171,7 @@ subtest 'the two models now answer the same questions' => sub {
 subtest 'running it again' => sub {
 subtest 'running it again' => sub {
     my ( $rc, $out ) = migrate();
     my ( $rc, $out ) = migrate();
     is( $rc, 0, 'succeeds' ) or diag($out);
     is( $rc, 0, 'succeeds' ) or diag($out);
-    like( $out, qr/Carried over 0 post version\(s\), skipped 6/, 'and carries nothing over twice' );
+    like( $out, qr/Carried[ ]over[ ]0[ ]post[ ]version\(s\),[ ]skipped[ ]6/, 'and carries nothing over twice' );
     is( $sqlite->count(), 4, 'leaving the post count alone' );
     is( $sqlite->count(), 4, 'leaving the post count alone' );
 };
 };
 
 
@@ -183,7 +184,7 @@ subtest 'picking up what was written in between' => sub {
 
 
     my ( $rc, $out ) = migrate();
     my ( $rc, $out ) = migrate();
     is( $rc, 0, 'succeeds' ) or diag($out);
     is( $rc, 0, 'succeeds' ) or diag($out);
-    like( $out, qr/Carried over 1 post version\(s\), skipped 6/, 'carrying over only the new one' );
+    like( $out, qr/Carried[ ]over[ ]1[ ]post[ ]version\(s\),[ ]skipped[ ]6/, 'carrying over only the new one' );
 
 
     my ($late) = $sqlite->get( limit => 0, acls => [qw{admin}], id => 'p-late' );
     my ($late) = $sqlite->get( limit => 0, acls => [qw{admin}], id => 'p-late' );
     is( $late->{title}, 'Written Later', 'which is there afterwards' );
     is( $late->{title}, 'Written Later', 'which is there afterwards' );