Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/src/history.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* Fix jam/{CPP} bind definitions of 4 or more values in a single
declared argument not actually adding all the definitions.
-- _Paolo Pastori_
* *New*: Added test for regular expressions with MATCH builtin rule.
-- _Paolo Pastori_

== Version 5.4.2

Expand Down
4 changes: 2 additions & 2 deletions test/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ time. All the elements in `names` should be relative paths.

. Stores the state of the working directory in `self.previous_tree`.
. Changes to `subdir`, if it is specified. It is relative to the
`original_workdir` or the workdir specified in `__init`.
`original_workdir` or the workdir specified in `+__init__+`.
. Invokes the `b2` executable, passing `extra_args` to it. The binary should be
located under `<test_invocation_dir>/../src/engine`. This is to make sure
tests use the version of `b2` build from source.
Expand Down Expand Up @@ -376,7 +376,7 @@ The members are:
* `expect_modification`
* `expect_nothing`

Note that `expect_modification` is used to check that a either file content or
Note that `expect_modification` is used to check if the file contents or
timestamp has changed. The rationale is that some compilers change content even
if sources does not change, and it's easier to have a method which checks for
both content and time changes.
Expand Down
94 changes: 94 additions & 0 deletions test/match_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python3

# Copyright 2026 Paolo Pastori
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)

# List of tested regexps. Each tuple contains the pattern,
# the value used for the test, and the expected result (do not use [] here).
# Any test is processed by an instruction such as
#
# ECHO [ MATCH <pattern> : <value> ] ;
#
# fell free to add more test cases...

# remember to use raw strings to avoid surprises with escaping
trials = [
(r'', r'', r''),
# nothing captured (no parentheses in the pattern)
(r'"hello world"', r'"hello world"', r''),
# captures (more than one)
(r'(first(second))(third)', r'firstsecondthird', r'firstsecond second third'),
# ^ matches at start of line
(r'^(world)', r'"hello world"', r''),
(r'(world)', r'"hello world"', r'world'),
# $ matches at end of line
(r'(hello)$', r'"hello world"', r''),
# . matches any single character
(r'(.)', r'x', r'x'),
# literal dot (with escaping looses special meaning)
# NOTE: a \ followed by one of the characters ^.[$()|*+?\
# matches that character taken as an ordinary character,
# while a \ followed by any other character (but <>) does nothing!
# NOTE: because of common escaping by shell/interpreters
# to obtain a final \ you often have to escape itself using
# \\ or enclose it in raw strings (Python r'..', C++ R"...")
(r'(\\.)', r'y', r''),
(r'(\\.)', r'.', r'.'),
# ? matches an optional atom, matches a sequence
# of 0 or 1 matches of the atom
(r'bar(s)?', r'bar', r''),
(r'bar(s)?', r'bars', r's'),
# + matches a sequence of 1 or more matches of the atom
(r'(cin)+', r'cin', r'cin'),
(r'((cin)+)', r'cincin', r'cincin cin'),
# * matches a sequence of 0 or more matches of the atom
(r'(0)*', r'1', r''),
(r'(0)*', r'1000', r''), # NOTE: this does not work as expected
# at the beginning of the pattern
(r'1(0)*', r'1000', r'0'),
(r'1(0)*1$', r'1001', r'0'),
# \< matches at the beginning of a word
(r'\\<(lo)', r'hello', r''),
(r'\\<(lo)', r"she's so lovely", r'lo'),
# \> matches at the end of a word
(r'"\\>( fi)"', r'fidel', r''),
(r'"\\>( fi)"', r'"hi fi"', r' fi'), # NOTE: extra space in result too
# | separate branches, matches anything that matches one of the branches
(r'(left)|(right)', r'left', r'left'),
(r'(left)|(right)', r'right', r' right'), # NOTE: extra space as first group is empty
# [] character class (list of characters enclosed in []), matches
# any single character from the list. If the list begins with ^, it
# matches any single character not from the rest of the list.
# If two characters in the list are separated by -, this is shorthand
# for the full range of characters between those two.
# To include a literal ] in the list, make it the first character
# (following a possible ^). To include a literal -, make it the first
# or last character. Within brackets special characters ^.[$()|*+?
# loose their special meaning.
(r'"([0-9]+)"', r'1980s', r'1980'),
(r'"([^0-9]+)"', r'1980s', r's'),
# some real life cases
(r'"^([0-9]+)\\.([0-9]+)(.*)$"', r'5.4.3beta', r'5 4 .3beta'),
(r'^@(.*)', r'@my-rule', r'my-rule'),
(r'^(!)?(.*)', r'"!bla bla"', r'! bla bla'),
]

# Do not change code below !

testln = []
exptln = []
for n, c in enumerate(trials):
testln.append('ECHO {} [ MATCH {} : {} ] ;'.format(n, c[0], c[1]))
exptln.append('{} {}'.format(n, c[2]) if c[2] else str(n))
testln.append('EXIT : 0 ;\n')
exptln.append('\n')

import BoostBuild

t = BoostBuild.Tester(pass_toolset=False)
t.write('Jamroot', '\n'.join(testln))
t.run_build_system()
t.expect_output_lines('\n'.join(exptln))
t.expect_nothing_more()
t.cleanup()
1 change: 1 addition & 0 deletions test/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ def reorder_tests(tests, first_test):
"load_order",
"loop",
"make_rule",
"match_list",
"message",
"ndebug",
"no_type",
Expand Down