Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 {0} [ MATCH {1} : {2} ] ;'.format(n, c[0], c[1]))
exptln.append('{0} {1}'.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
Loading