Skip to content

mruby-regexp: split the test file by subject - #7088

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:regexp-test-split
Aug 11, 2026
Merged

mruby-regexp: split the test file by subject#7088
matz merged 1 commit into
mruby:masterfrom
takumin:regexp-test-split

Conversation

@takumin

@takumin takumin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

test/regexp.rb had grown to 3338 lines and 219 cases holding every subject at
once, three times the size of the largest test file elsewhere in the tree
(mruby-string-ext's string.rb, at 974 lines). Finding the cases about one
subject meant scanning past all the others.

The tree splits test files by tested class: mruby-io keeps io.rb, file.rb and
file_test.rb, mruby-task keeps task.rb and queue.rb, and mruby-string-ext
splits by the class its methods land on. This suite is too large for that cut
alone, since the String integration is half of it by itself, so it splits one
level finer, by subject:

  • test/regexp.rb (264 lines): the class API. Construction,
    match/match?/=~/===, Symbol subjects, and the object protocol
    (escape, inspect, to_s, ==, hash, options, casefold?).
  • test/regexp_syntax.rb (876): what patterns mean. Classes, quantifiers,
    alternation, anchors, groups, backreferences, named captures, lookaround,
    inline options, /i and /x.
  • test/regexp_utf8.rb (481): characters against bytes. Multibyte atoms,
    \u escapes, invalid and overlong sequences, byte classes, binary subjects.
  • test/match_data.rb (219): MatchData and the match globals.
  • test/string_regexp.rb (776): the String methods this gem defines:
    match, =~, sub, gsub, scan, split, and their dispatch checks
    with the helper classes they share.
  • test/string_index.rb (717): the core String methods this gem extends with
    a regexp form: [], []=, slice!, the index family, partition,
    start_with?, and the override surface.

The three build-conditional files (ascii_case.rb, unicode_case.rb,
symbol_regexp.rb) are untouched.

A pure move

Every block is carried verbatim in its original order, and no block changes
meaning by moving: the multiset of non-blank lines is identical before and
after the split. One title reads as String but stays put. "String#split and
String#scan see the empty iteration's capture" belongs to the empty-iteration
quartet in regexp_syntax.rb, which argues about the engine, not about String.

spec.test_rbfiles globs the test directory, so the build picks the new
files up as it stands. The conditional subtraction in mrbgem.rake names only
the two case-folding files and is unaffected; its comment now points at the
unconditional files as a group rather than at test/regexp.rb by name.

rake test is clean: 2049 cases, 0 failures, and the same skips as before
the split.

Summary by CodeRabbit

  • Tests
    • Expanded coverage for regular-expression matching, captures, named groups, global match variables, and syntax validation.
    • Added extensive UTF-8 and binary-string tests, including invalid encodings, character classes, offsets, escapes, and case folding.
    • Added regression tests for string searching, slicing, splitting, scanning, substitution, replacement behavior, zero-width matches, and error handling.
    • Improved validation of subclass behavior, frozen strings, mutation safety, and edge-case limits.

test/regexp.rb had grown to 3338 lines holding every subject at once,
three times the size of the largest test file elsewhere in the tree.
The other gems split by tested class (mruby-io keeps io.rb, file.rb and
file_test.rb; mruby-task keeps task.rb and queue.rb); this suite is too
large for that alone, so it splits one level finer, by subject:

- regexp.rb: the class API. Construction, `match`/`match?`/`=~`/`===`,
  Symbol subjects, and the object protocol (`escape`, `inspect`,
  `to_s`, `==`, `hash`, `options`, `casefold?`).
- regexp_syntax.rb: what patterns mean. Classes, quantifiers,
  alternation, anchors, groups, backreferences, named captures,
  lookaround, inline options, /i and /x.
- regexp_utf8.rb: characters against bytes. Multibyte atoms, `\u`
  escapes, invalid and overlong sequences, byte classes, binary
  subjects.
- match_data.rb: `MatchData` and the match globals.
- string_regexp.rb: the String methods this gem defines. `match`,
  `=~`, `sub`, `gsub`, `scan`, `split`, and their dispatch checks
  with the helper classes they share.
- string_index.rb: the core String methods this gem extends with a
  regexp form. `[]`, `[]=`, `slice!`, the index family, `partition`,
  `start_with?`, and the override surface.

A pure move: every block is carried verbatim in its original order,
and no block changes meaning by moving. "String#split and String#scan
see the empty iteration's capture" stays with the empty-iteration
quartet in regexp_syntax.rb because it argues about the engine, not
about String.

`spec.test_rbfiles` globs the test directory, so the build picks the
new files up as it stands; the conditional subtraction in mrbgem.rake
names only the two case-folding files and is unaffected. Its comment
now points at the unconditional files as a group rather than at
test/regexp.rb by name.
@takumin
takumin requested a review from matz as a code owner August 10, 2026 23:57
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds extensive regression tests for mruby regexp syntax, UTF-8 behavior, MatchData, match globals, and regexp-aware String operations.

Changes

Regexp behavior

Layer / File(s) Summary
Regexp syntax and compiler behavior
mrbgems/mruby-regexp/mrbgem.rake, mrbgems/mruby-regexp/test/regexp_syntax.rb
Adds coverage for syntax, captures, options, backreferences, lookarounds, escapes, malformed patterns, and compiler bounds.
UTF-8 and byte matching
mrbgems/mruby-regexp/test/regexp_utf8.rb
Adds coverage for UTF-8 boundaries, Unicode escapes and classes, invalid encodings, byte semantics, case folding, and large character classes.
MatchData and match globals
mrbgems/mruby-regexp/test/match_data.rb
Adds coverage for captures, snapshots, offsets, named groups, invalid lookups, and match-global variables.
String regexp operations
mrbgems/mruby-regexp/test/string_regexp.rb
Adds coverage for scan, match, substitution, splitting, dispatch validation, enumerators, replacement behavior, limits, and match-global preservation.
Regexp-aware String indexing and search
mrbgems/mruby-regexp/test/string_index.rb
Adds coverage for indexing, slicing, partitioning, prefix checks, byte searches, delegation, subclass results, and overridden regexp methods.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • mruby/mruby#7053: Adds implementation changes for subject snapshots and preserved match globals covered by these tests.
  • mruby/mruby#7070: Changes UTF-8 match-boundary validation covered by the new regexp tests.
  • mruby/mruby#7001: Adds pattern-type validation covered by the String regexp operation tests.

Suggested reviewers: matz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: splitting the mruby-regexp test file by subject.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
mrbgems/mruby-regexp/test/regexp_utf8.rb (1)

468-474: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Build the pattern with << instead of +=.

The loop runs about 32,769 times. Each s += ... allocates a new String and copies the whole accumulated buffer, so the loop copies on the order of a gigabyte to build a ~90 KB pattern. String#<< appends in place and makes the loop linear. The resulting pattern is identical.

♻️ Proposed change
   s = "["
   i = 0x80
   while i <= 0x8080
-    s += utf8.call(i)
+    s << utf8.call(i)
     i += 1
   end
-  s += "]"
+  s << "]"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mrbgems/mruby-regexp/test/regexp_utf8.rb` around lines 468 - 474, Update the
pattern-building loop in the UTF-8 regexp test to append each utf8.call(i)
result in place with String#<< instead of allocating through +=; preserve the
existing loop bounds and resulting pattern contents.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@mrbgems/mruby-regexp/test/regexp_utf8.rb`:
- Around line 468-474: Update the pattern-building loop in the UTF-8 regexp test
to append each utf8.call(i) result in place with String#<< instead of allocating
through +=; preserve the existing loop bounds and resulting pattern contents.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a3c14d4-647e-43da-a77b-360246358f80

📥 Commits

Reviewing files that changed from the base of the PR and between 7d18805 and f311467.

📒 Files selected for processing (7)
  • mrbgems/mruby-regexp/mrbgem.rake
  • mrbgems/mruby-regexp/test/match_data.rb
  • mrbgems/mruby-regexp/test/regexp.rb
  • mrbgems/mruby-regexp/test/regexp_syntax.rb
  • mrbgems/mruby-regexp/test/regexp_utf8.rb
  • mrbgems/mruby-regexp/test/string_index.rb
  • mrbgems/mruby-regexp/test/string_regexp.rb

@takumin

takumin commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

The "pure move" claim above is checkable mechanically. Save the script below next to the repository and run it on this branch:

$ sh verify-split.sh
ok   lines: 2928 identical
ok   blocks: 219 identical
ok   mrbgems/mruby-regexp/test/match_data.rb: 24 cases in original order
ok   mrbgems/mruby-regexp/test/regexp.rb: 30 cases in original order
ok   mrbgems/mruby-regexp/test/regexp_syntax.rb: 63 cases in original order
ok   mrbgems/mruby-regexp/test/regexp_utf8.rb: 22 cases in original order
ok   mrbgems/mruby-regexp/test/string_index.rb: 31 cases in original order
ok   mrbgems/mruby-regexp/test/string_regexp.rb: 49 cases in original order
PASS: a pure move

It concatenates the touched .rb files under the test directory on each side of the diff and checks three things:

  1. the multiset of non-blank lines is identical, so no line was added, dropped or edited anywhere, inside a case or outside one;
  2. the multiset of whole assert blocks is identical, so no line moved between two cases (1 alone would not catch that);
  3. the case titles in each destination file are a subsequence of the original title sequence, so no case overtook another on the way.

Together these say every case is carried verbatim and each new file keeps the relative order the original had. They say nothing about which file a case landed in; that is the judgement the PR body argues for.

The script fails on the three mutations I seeded to check it: an edited title (caught by 1), two blocks swapped (3), and two lines swapped inside one block (2).

#!/bin/sh
# Check that this PR only moves test code around.
#
#   sh verify-split.sh [BASE] [HEAD]    # defaults: merge-base with origin/master, and HEAD
set -eu

dir=mrbgems/mruby-regexp/test
base=${1:-$(git merge-base HEAD origin/master)}
head=${2:-HEAD}

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

# Every .rb under the test dir that the diff touches, on either side.
git diff --name-only "$base" "$head" -- "$dir/*.rb" >"$tmp/paths"

cat_rev() {
  : >"$2"
  while IFS= read -r p; do
    if git cat-file -e "$1:$p" 2>/dev/null; then git show "$1:$p" >>"$2"; fi
  done <"$tmp/paths"
}
cat_rev "$base" "$tmp/before.rb"
cat_rev "$head" "$tmp/after.rb"

lines()  { grep -v '^[[:space:]]*$' "$1" | LC_ALL=C sort; }
titles() { grep '^assert' "$1" || true; }
# One line per assert block, blank lines dropped, so blocks compare as units.
blocks() {
  awk '/^assert\(/ { if (n) print b; b = $0; n = 1; next }
       n && $0 !~ /^[[:space:]]*$/ { b = b " | " $0 }
       END { if (n) print b }' "$1" | LC_ALL=C sort
}

check() {
  "$1" "$tmp/before.rb" >"$tmp/b.$1"
  "$1" "$tmp/after.rb"  >"$tmp/a.$1"
  if diff -u "$tmp/b.$1" "$tmp/a.$1" >"$tmp/d.$1"; then
    echo "ok   $1: $(wc -l <"$tmp/b.$1") identical"
  else
    echo "FAIL $1 differ:"; sed -n '3,30p' "$tmp/d.$1" | cut -c1-100; exit 1
  fi
}

check lines    # nothing added, dropped or edited, anywhere in these files
check blocks   # every assert block carried verbatim, as a unit

# Each destination file keeps its cases in the order the original had them.
titles "$tmp/before.rb" >"$tmp/before.titles"
while IFS= read -r p; do
  git cat-file -e "$head:$p" 2>/dev/null || continue
  git show "$head:$p" >"$tmp/f.rb"
  titles "$tmp/f.rb" >"$tmp/f.titles"
  awk -v f="$p" '
    NR == FNR { o[++n] = $0; next }
    { m++
      while (i < n && o[i+1] != $0) i++
      if (i >= n) { print "  out of order: " $0; bad = 1; exit 1 }
      i++ }
    END { if (!bad) printf "ok   %s: %d cases in original order\n", f, m }
  ' "$tmp/before.titles" "$tmp/f.titles" || { echo "FAIL $p reorders cases"; exit 1; }
done <"$tmp/paths"

echo "PASS: a pure move"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants