Add `assert_match` and `assert_not_match`
MODIFY
mrbgems/mruby-test/driver.c
mrbgems/mruby-test/driver.c
@@ -62,6 +62,151 @@ t_print(mrb_state *mrb, mrb_value self)
return mrb_nil_value();
}
+#define UNESCAPE(p, endp) ((p) != (endp) && *(p) == '\\' ? (p)+1 : (p))
+#define CHAR_CMP(c1, c2) ((unsigned char)(c1) - (unsigned char)(c2))
+
+static const char *
+str_match_bracket(const char *p, const char *pat_end,
+ const char *s, const char *str_end)
+{
+ mrb_bool ok = FALSE, negated = FALSE;
+
+ if (p == pat_end) return NULL;
+ if (*p == '!' || *p == '^') {
+ negated = TRUE;
+ ++p;
+ }
+
+ while (*p != ']') {
+ const char *t1 = p;
+ if ((t1 = UNESCAPE(t1, pat_end)) == pat_end) return NULL;
+ if ((p = t1 + 1) == pat_end) return NULL;
+ if (p[0] == '-' && p[1] != ']') {
+ const char *t2 = p + 1;
+ if ((t2 = UNESCAPE(t2, pat_end)) == pat_end) return NULL;
+ p = t2 + 1;
+ if (!ok && CHAR_CMP(*t1, *s) <= 0 && CHAR_CMP(*s, *t2) <= 0) ok = TRUE;
+ }
+ else {
+ if (!ok && CHAR_CMP(*t1, *s) == 0) ok = TRUE;
+ }
+ }
+
+ return ok == negated ? NULL : p + 1;
+}
+
+static mrb_bool
+str_match_no_brace_p(const char *pat, mrb_int pat_len,
+ const char *str, mrb_int str_len)
+{
+ const char *p = pat, *s = str;
+ const char *pat_end = pat + pat_len, *str_end = str + str_len;
+ const char *p_tmp = NULL, *s_tmp = NULL;
+
+ for (;;) {
+ if (p == pat_end) return s == str_end;
+ switch (*p) {
+ case '*':
+ do { ++p; } while (p != pat_end && *p == '*');
+ if (UNESCAPE(p, pat_end) == pat_end) return TRUE;
+ if (s == str_end) return FALSE;
+ p_tmp = p;
+ s_tmp = s;
+ continue;
+ case '?':
+ if (s == str_end) return FALSE;
+ ++p;
+ ++s;
+ continue;
+ case '[': {
+ const char *t;
+ if (s == str_end) return FALSE;
+ if ((t = str_match_bracket(p+1, pat_end, s, str_end))) {
+ p = t;
+ ++s;
+ continue;
+ }
+ goto L_failed;
+ }
+ }
+
+ /* ordinary */
+ p = UNESCAPE(p, pat_end);
+ if (s == str_end) return p == pat_end;
+ if (p == pat_end) goto L_failed;
+ if (*p++ != *s++) goto L_failed;
+ continue;
+
+ L_failed:
+ if (p_tmp && s_tmp) {
+ /* try next '*' position */
+ p = p_tmp;
+ s = ++s_tmp;
+ continue;
+ }
+
+ return FALSE;
+ }
+}
+
+#define COPY_AND_INC(dst, src, len) \
+ do { memcpy(dst, src, len); dst += len; } while (0)
+
+static mrb_bool
+str_match_p(mrb_state *mrb,
+ const char *pat, mrb_int pat_len,
+ const char *str, mrb_int str_len)
+{
+ const char *p = pat, *pat_end = pat + pat_len;
+ const char *lbrace = NULL, *rbrace = NULL;
+ int nest = 0;
+ mrb_bool ret = FALSE;
+
+ for (; p != pat_end; ++p) {
+ if (*p == '{' && nest++ == 0) lbrace = p;
+ else if (*p == '}' && lbrace && --nest == 0) { rbrace = p; break; }
+ else if (*p == '\\' && ++p == pat_end) break;
+ }
+
+ if (lbrace && rbrace) {
+ /* expand brace */
+ char *ex_pat = (char *)mrb_malloc(mrb, pat_len-2); /* expanded pattern */
+ char *ex_p = ex_pat;
+
+ COPY_AND_INC(ex_p, pat, lbrace-pat);
+ p = lbrace;
+ while (p < rbrace) {
+ char *orig_ex_p = ex_p;
+ const char *t = ++p;
+ for (nest = 0; p < rbrace && !(*p == ',' && nest == 0); ++p) {
+ if (*p == '{') ++nest;
+ else if (*p == '}') --nest;
+ else if (*p == '\\' && ++p == rbrace) break;
+ }
+ COPY_AND_INC(ex_p, t, p-t);
+ COPY_AND_INC(ex_p, rbrace+1, pat_end-rbrace-1);
+ if ((ret = str_match_p(mrb, ex_pat, ex_p-ex_pat, str, str_len))) break;
+ ex_p = orig_ex_p;
+ }
+ mrb_free(mrb, ex_pat);
+ }
+ else if (!lbrace && !rbrace) {
+ ret = str_match_no_brace_p(pat, pat_len, str, str_len);
+ }
+
+ return ret;
+}
+
+static mrb_value
+m_str_match_p(mrb_state *mrb, mrb_value self)
+{
+ const char *pat, *str;
+ mrb_int pat_len, str_len;
+
+ mrb_get_args(mrb, "ss", &pat, &pat_len, &str, &str_len);
+ return mrb_bool_value(str_match_p(mrb, pat, pat_len, str, str_len));
+}
+
void
mrb_init_test_driver(mrb_state *mrb, mrb_bool verbose)
{
@@ -69,6 +214,7 @@ mrb_init_test_driver(mrb_state *mrb, mrb_bool verbose)
krn = mrb->kernel_module;
mrb_define_method(mrb, krn, "t_print", t_print, MRB_ARGS_ANY());
+ mrb_define_method(mrb, krn, "_str_match?", m_str_match_p, MRB_ARGS_REQ(2));
mrbtest = mrb_define_module(mrb, "Mrbtest");

MODIFY
test/assert.rb
test/assert.rb
@@ -136,6 +136,39 @@ def _assert_include(affirmed, collection, obj, msg = nil)
assert_true(ret, msg, diff)
end
+##
+# Fail unless +str+ matches against +pattern+.
+#
+# +pattern+ is interpreted as pattern for File.fnmatch?. It may contain the
+# following metacharacters:
+#
+# <code>*</code> ::
+# Matches any string.
+#
+# <code>?</code> ::
+# Matches any one character.
+#
+# <code>[_SET_]</code>, <code>[^_SET_]</code> (<code>[!_SET_]</code>) ::
+# Matches any one character in _SET_. Behaves like character sets in
+# Regexp, including set negation (<code>[^a-z]</code>).
+#
+# <code>{_A_,_B_}</code> ::
+# Matches pattern _A_ or pattern _B_.
+#
+# <code> \ </code> ::
+# Escapes the next character.
+def assert_match(*args); _assert_match(true, *args) end
+def assert_not_match(*args); _assert_match(false, *args) end
+def _assert_match(affirmed, pattern, str, msg = nil)
+ receiver, *args = RUBY_ENGINE == "mruby" ?
+ [self, :_str_match?, pattern, str] :
+ [File, :fnmatch?, pattern, str, File::FNM_EXTGLOB|File::FNM_DOTMATCH]
+ unless ret = !receiver.__send__(*args) == !affirmed
+ diff = " Expected #{pattern.inspect} to #{'not ' unless affirmed}match #{str.inspect}."
+ end
+ assert_true(ret, msg, diff)
+end
+
##
# Fails unless +obj+ is a kind of +cls+.
def assert_kind_of(cls, obj, msg = nil)

MODIFY
test/t/module.rb
test/t/module.rb
@@ -640,11 +640,8 @@ assert('Module#to_s') do
assert_equal 'SetOuter', SetOuter.to_s
assert_equal 'SetOuter::SetInner', SetOuter::SetInner.to_s
- mod = Module.new
- cls = Class.new
-
- assert_equal "#<Module:0x", mod.to_s[0,11]
- assert_equal "#<Class:0x", cls.to_s[0,10]
+ assert_match "#<Module:0x*>", Module.new.to_s
+ assert_match "#<Class:0x*>", Class.new.to_s
end
assert('Module#inspect') do

Add `assert_match` and `assert_not_match`