Merge branch 'master' into tests_for_bigint

This commit is contained in:
Hoshiumi Arata
2024-12-09 22:38:32 +09:00
13 changed files with 174 additions and 84 deletions
+8 -8
View File
@@ -1,9 +1,9 @@
# Authors of mruby (mruby developers)
## The List of Contributors sorted by number of commits (as of 2024-11-07 90764b1)
## The List of Contributors sorted by number of commits (as of 2024-12-04 1602844)
5895 Yukihiro "Matz" Matsumoto (@matz)*
638 dearblue (@dearblue)*
5922 Yukihiro "Matz" Matsumoto (@matz)*
648 dearblue (@dearblue)*
587 KOBAYASHI Shuji (@shuujii)
353 Daniel Bovensiepen (@bovi)*
345 Takeshi Watanabe (@take-cheeze)*
@@ -11,7 +11,7 @@
234 Jun Hiroe (@suzukaze)
228 Tomoyuki Sahara (@tsahara)*
220 Cremno (@cremno)*
215 John Bampton (@jbampton)
219 John Bampton (@jbampton)
209 Yuki Kurihara (@ksss)+
144 Yasuhiro Matsumoto (@mattn)*
113 Carson McDonald (@carsonmcdonald)
@@ -38,6 +38,7 @@
30 Nobuyoshi Nakada (@nobu)
25 Julian Aron Prenner (@furunkel)*
22 Clayton Smith (@clayton-shopify)
22 Hoshiumi Arata (@hoshiumiarata)*
22 Uchio Kondo (@udzura)*
22 Zachary Scott (@zzak)*
21 Ryan Lopopolo (@lopopolo)
@@ -119,7 +120,6 @@
4 HASUMI Hitoshi (@hasumikin)
4 Herwin Weststrate (@herwinw)
4 Horimoto Yasuhiro (@komainu8)
4 Hoshiumi Arata (@hoshiumiarata)
4 Jon Moss (@maclover7)
4 Ken Muramatsu (@ken-mu)+
4 Kohei Suzuki (@eagletmt)
@@ -136,6 +136,7 @@
3 Anton Davydov (@davydovanton)
3 Aurora Nockert (@auroranockert)
3 Carlo Prelz (@asfluido)*
3 Daniel K. Sierpiński (@513ry)+
3 David Turnbull (@AE9RB)
3 Franck Verrot (@franckverrot)
3 J. Mutua (@katmutua)+
@@ -159,9 +160,7 @@
3 William Light (@wrl)
3 bamchoh (@bamchoh)
3 sasaki takeru (@takeru)
3 Daniel K. Sierpiński (@513ry)+
3 windwiny (@windwiny)
3 星湖新 (@hoshiumiarata)
2 Akira Moroo (@retrage)
2 Artur K (@nemerle)
2 Christian Mauceri (@mauceri)
@@ -206,7 +205,6 @@
1 Alex Wang (@nanamiwang)+
1 AlexDenisov (@AlexDenisov)
1 Andrew Nordman (@cadwallion)
1 Sinkevich Artem (@ArtSin)+
1 Ashish Kurmi (@boahc077)
1 Atsushi Morimoto (@mynz)
1 Ben A Morgan (@BenMorganIO)
@@ -254,6 +252,7 @@
1 Martin Bosslet (@emboss)+
1 Masahiko Sawada (@MasahikoSawada)
1 Matt Aimonetti (@mattetti)
1 Max Base (@MaxFork)
1 Maxim Abramchuk (@MaximAbramchuck)
1 Megumi Tomita (@tomykaira)+
1 Mitchell Hashimoto (@mitchellh)
@@ -278,6 +277,7 @@
1 Sayed Abdelhaleem (@visualsayed)
1 Sergey Ukrainskiy (@ukrainskiysergey)
1 Shugo Maeda (@shugo)
1 Sinkevich Artem (@ArtSin)
1 Sorah Fukumori (@sorah)
1 Stephen Jothen (@sjothen)
1 Stuart Hinson (@stuarth)
+32 -41
View File
@@ -312,18 +312,16 @@ mpz_add(mrb_state *mrb, mpz_t *zz, mpz_t *x, mpz_t *y)
mpz_move(mrb, zz, &z);
}
/* x = x + n (only called from mpz_init_set_str) */
/* assumes x and n are positive or zero */
/* assumes n is small (fits in mp_limb) */
/* x += n */
/* ignores sign of x */
/* assumes n is positive and small (fits in mp_limb) */
static void
mpz_add_int(mrb_state *mrb, mpz_t *x, mrb_int n)
{
if (n == 0) {
// If n is zero, no operation is needed
return;
}
// If n is zero, no operation is needed
if (n == 0) return;
// Assume x is positive and n is a small positive integer (n < 36)
// Assume x is positive and n is a small positive integer
mp_dbl_limb carry = n; // Initialize carry with n
for (size_t i = 0; i < x->sz && carry; i++) {
carry += (mp_dbl_limb)x->p[i]; // Add current limb and carry
@@ -352,20 +350,20 @@ mpz_sub(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y)
mpz_add(mrb, z, x, &u);
}
/* x -= n */
/* assumes n is small (fits in mp_limb) */
/* x -= n */
/* ignores sign of x */
/* assumes n is positive and small (fits in mp_limb) */
static void
mpz_sub_int(mrb_state *mrb, mpz_t *x, mrb_int n)
{
// If x is zero, set x to -1
// If n is zero, no operation is needed
if (n == 0) return;
// If x is zero, set x to n
if (zero_p(x) || x->sz == 0) {
mpz_set_int(mrb, x, n);
return;
}
if (x->sn < 0) {
mpz_add_int(mrb, x, 1);
return;
}
// Initialize borrow and start decrement
mp_dbl_limb_signed borrow = (mp_limb)n;
@@ -897,7 +895,7 @@ mpz_get_str(mrb_state *mrb, char *s, mrb_int sz, mrb_int base, mpz_t *x)
mp_limb *t = (mp_limb*)mrb_malloc(mrb, xlen*sizeof(mp_limb));
mp_limb *tend = t + xlen;
memcpy(t, x->p, xlen*sizeof(mp_limb));
mp_limb b2 = base_limit[(base-3)];
mp_limb b2 = base_limit[base-3];
for (;;) {
mp_limb *d = tend;
@@ -910,12 +908,12 @@ mpz_get_str(mrb_state *mrb, char *s, mrb_int sz, mrb_int base, mpz_t *x)
}
// convert to character
while (a > 0) {
mp_limb a0 = (mp_limb)(a % base);
for (mp_limb b=b2/base; b>0; b/=base) {
char a0 = (char)(a % base);
if (a0 < 10) a0 += '0';
else a0 += 'a' - 10;
if (s == se) break;
*s++ = (char)a0;
*s++ = a0;
a /= base;
}
@@ -1480,22 +1478,17 @@ mrb_bint_as_uint64(mrb_state *mrb, mrb_value x)
static mrb_bool
int_fit_limb_p(mrb_int i)
{
#ifdef MRB_INT64
#if DIG_SIZE == 32
# ifdef MRB_INT64
// if mp_limb is int32_t
return (i >= INT32_MIN && i <= INT32_MAX);
#else /* if DIG_SIZE == 16 */
// if mp_limb is int16_t
return (i >= INT16_MIN && i <= INT16_MAX);
#endif
#else /* MRB_INT32 */
#if DIG_SIZE == 32
return (i > INT32_MIN && i <= INT32_MAX);
# else
// if mp_limb is also int32_t, it always fits
return TRUE;
# endif
#else /* if DIG_SIZE == 16 */
// if mp_limb is int16_t
return (i >= INT16_MIN && i <= INT16_MAX);
#endif
return (i > INT16_MIN && i <= INT16_MAX);
#endif
}
@@ -1507,17 +1500,15 @@ mrb_bint_add_n(mrb_state *mrb, mrb_value x, mrb_value y)
bint_as_mpz(RBIGINT(x), &a);
if (mrb_integer_p(y)) {
mrb_int i = mrb_integer(y);
if (int_fit_limb_p(i)) {
mrb_int n = mrb_integer(y);
if (int_fit_limb_p(n)) {
mpz_init_set(mrb, &z, &a);
if ((i > 0) ^ (z.sn > 0)) {
mpz_sub_int(mrb, &z, i);
if ((n > 0) ^ (z.sn > 0)) {
mpz_sub_int(mrb, &z, n<0 ? -n : n);
}
else {
mpz_add_int(mrb, &z, i);
mpz_add_int(mrb, &z, n<0 ? -n : n);
}
struct RBigint *v = bint_new(mrb, &z);
return mrb_obj_value(v);
}
}
y = mrb_as_bint(mrb, y);
@@ -1550,14 +1541,14 @@ mrb_bint_sub_n(mrb_state *mrb, mrb_value x, mrb_value y)
bint_as_mpz(RBIGINT(x), &a);
if (mrb_integer_p(y)) {
mrb_int i = mrb_integer(y);
if (int_fit_limb_p(i)) {
mrb_int n = mrb_integer(y);
if (int_fit_limb_p(n)) {
mpz_init_set(mrb, &z, &a);
if ((i > 0) ^ (z.sn > 0)) {
mpz_add_int(mrb, &z, i);
if ((n > 0) ^ (z.sn > 0)) {
mpz_add_int(mrb, &z, n<0 ? -n : n);
}
else {
mpz_sub_int(mrb, &z, i);
mpz_sub_int(mrb, &z, n<0 ? -n : n);
}
struct RBigint *v = bint_new(mrb, &z);
return mrb_obj_value(v);
+2 -2
View File
@@ -14,7 +14,7 @@
#include <mruby/error.h>
#include <mruby/presym.h>
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
# include <io.h> /* for setmode */
# include <fcntl.h>
#endif
@@ -244,7 +244,7 @@ parse_args(mrb_state *mrb, int argc, char **argv, struct _args *args)
argc--; argv++;
}
}
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
if (args->rfp == stdin) {
_setmode(_fileno(stdin), O_BINARY);
}
+10 -5
View File
@@ -1322,6 +1322,7 @@ for_body(codegen_scope *s, node *tree)
/* construct loop */
lp = loop_push(s, LOOP_FOR);
lp->pc1 = new_label(s);
genop_0(s, OP_NOP); /* for redo */
/* loop body */
codegen(s, tree->cdr->cdr->car, VAL);
@@ -2588,6 +2589,7 @@ codegen(codegen_scope *s, node *tree, int val)
pos = genjmp2_0(s, OP_JMPIF, cursp(), NOVAL);
}
lp->pc1 = new_label(s);
genop_0(s, OP_NOP); /* for redo */
codegen(s, tree->cdr, NOVAL);
genjmp(s, OP_JMP, lp->pc0);
dispatch(s, pos);
@@ -3182,11 +3184,14 @@ codegen(codegen_scope *s, node *tree, int val)
break;
case NODE_REDO:
if (!s->loop || s->loop->type == LOOP_BEGIN || s->loop->type == LOOP_RESCUE) {
raise_error(s, "unexpected redo");
}
else {
genjmp(s, OP_JMPUW, s->loop->pc1);
for (const struct loopinfo *lp = s->loop; ; lp = lp->prev) {
if (!lp) {
raise_error(s, "unexpected redo");
}
if (lp->type != LOOP_BEGIN && lp->type != LOOP_RESCUE) {
genjmp(s, OP_JMPUW, lp->pc1);
break;
}
}
if (val) push();
break;
+4 -4
View File
@@ -11,7 +11,7 @@
#include <mruby/string.h>
#include <mruby/presym.h>
#include <sys/types.h>
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
#define MAXPATHLEN 1024
#if !defined(PATH_MAX)
#define PATH_MAX MAX_PATH
@@ -173,7 +173,7 @@ mrb_dir_chdir(mrb_state *mrb, mrb_value klass)
static mrb_value
mrb_dir_chroot(mrb_state *mrb, mrb_value self)
{
#if defined(_WIN32) || defined(_WIN64) || defined(__ANDROID__) || defined(__MSDOS__)
#if defined(_WIN32) || defined(__ANDROID__) || defined(__MSDOS__)
mrb_raise(mrb, E_NOTIMP_ERROR, "chroot() unreliable on your system");
return mrb_fixnum_value(0);
#else
@@ -259,7 +259,7 @@ mrb_dir_rewind(mrb_state *mrb, mrb_value self)
static mrb_value
mrb_dir_seek(mrb_state *mrb, mrb_value self)
{
#if defined(_WIN32) || defined(_WIN64) || defined(__ANDROID__)
#if defined(_WIN32) || defined(__ANDROID__)
mrb_raise(mrb, E_NOTIMP_ERROR, "dirseek() unreliable on your system");
return self;
#else
@@ -280,7 +280,7 @@ mrb_dir_seek(mrb_state *mrb, mrb_value self)
static mrb_value
mrb_dir_tell(mrb_state *mrb, mrb_value self)
{
#if defined(_WIN32) || defined(_WIN64) || defined(__ANDROID__)
#if defined(_WIN32) || defined(__ANDROID__)
mrb_raise(mrb, E_NOTIMP_ERROR, "dirtell() unreliable on your system");
return mrb_fixnum_value(0);
#else
+2 -2
View File
@@ -5,7 +5,7 @@
#include <stdlib.h>
#include <string.h>
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
#ifdef __cplusplus
extern "C" {
#endif
@@ -60,7 +60,7 @@ mrb_dirtest_setup(mrb_state *mrb, mrb_value klass)
mrb_cv_set(mrb, klass, mrb_intern_cstr(mrb, "pwd"), mrb_str_new_cstr(mrb, buf));
/* create sandbox */
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
snprintf(buf, sizeof(buf), "%s\\mruby-dir-test.XXXXXX", _getcwd(NULL,0));
if ((_mktemp(buf) == NULL) || mkdir(buf,0) != 0) {
mrb_raisef(mrb, E_RUNTIME_ERROR, "mkdtemp(%s) failed", buf);
+9 -9
View File
@@ -18,7 +18,7 @@
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
#include <windows.h>
#include <io.h>
#define NULL_FILE "NUL"
@@ -47,7 +47,7 @@
#define FILE_SEPARATOR "/"
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
#define PATH_SEPARATOR ";"
#define FILE_ALT_SEPARATOR "\\"
#define VOLUME_SEPARATOR ":"
@@ -99,7 +99,7 @@ flock(int fd, int operation)
static mrb_value
mrb_file_s_umask(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
/* nothing to do on windows */
return mrb_fixnum_value(0);
@@ -146,7 +146,7 @@ mrb_file_s_rename(mrb_state *mrb, mrb_value obj)
char *src = mrb_locale_from_utf8(RSTRING_CSTR(mrb, from), -1);
char *dst = mrb_locale_from_utf8(RSTRING_CSTR(mrb, to), -1);
if (rename(src, dst) < 0) {
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
if (CHMOD(dst, 0666) == 0 && UNLINK(dst) == 0 && rename(src, dst) == 0) {
mrb_locale_free(src);
mrb_locale_free(dst);
@@ -166,7 +166,7 @@ mrb_file_s_rename(mrb_state *mrb, mrb_value obj)
static mrb_value
mrb_file_dirname(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
char dname[_MAX_DIR], vname[_MAX_DRIVE];
char buffer[_MAX_DRIVE + _MAX_DIR];
const char *utf8_path;
@@ -205,7 +205,7 @@ static mrb_value
mrb_file_basename(mrb_state *mrb, mrb_value klass)
{
// NOTE: Do not use mrb_locale_from_utf8 here
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
char bname[_MAX_DIR];
char extname[_MAX_EXT];
char buffer[_MAX_DIR + _MAX_EXT];
@@ -535,7 +535,7 @@ mrb_file_truncate(mrb_state *mrb, mrb_value self)
static mrb_value
mrb_file_s_symlink(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on this platform");
#else
mrb_value from, to;
@@ -580,7 +580,7 @@ mrb_file_s_chmod(mrb_state *mrb, mrb_value klass)
static mrb_value
mrb_file_s_readlink(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
mrb_raise(mrb, E_NOTIMP_ERROR, "readlink is not supported on this platform");
return mrb_nil_value(); // unreachable
#else
@@ -646,7 +646,7 @@ mrb_init_file(mrb_state *mrb)
mrb_define_const_id(mrb, cnst, MRB_SYM(LOCK_NB), mrb_fixnum_value(LOCK_NB));
mrb_define_const_id(mrb, cnst, MRB_SYM(SEPARATOR), mrb_str_new_cstr(mrb, FILE_SEPARATOR));
mrb_define_const_id(mrb, cnst, MRB_SYM(PATH_SEPARATOR), mrb_str_new_cstr(mrb, PATH_SEPARATOR));
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
mrb_define_const_id(mrb, cnst, MRB_SYM(ALT_SEPARATOR), mrb_str_new_cstr(mrb, FILE_ALT_SEPARATOR));
#else
mrb_define_const_id(mrb, cnst, MRB_SYM(ALT_SEPARATOR), mrb_nil_value());
+4 -4
View File
@@ -12,7 +12,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
#define LSTAT stat
#include <winsock.h>
#else
@@ -118,7 +118,7 @@ mrb_filetest_s_directory_p(mrb_state *mrb, mrb_value klass)
static mrb_value
mrb_filetest_s_pipe_p(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
mrb_raise(mrb, E_NOTIMP_ERROR, "pipe is not supported on this platform");
#else
#ifdef S_IFIFO
@@ -149,7 +149,7 @@ mrb_filetest_s_pipe_p(mrb_state *mrb, mrb_value klass)
static mrb_value
mrb_filetest_s_symlink_p(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on this platform");
#else
#ifndef S_ISLNK
@@ -190,7 +190,7 @@ mrb_filetest_s_symlink_p(mrb_state *mrb, mrb_value klass)
static mrb_value
mrb_filetest_s_socket_p(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
mrb_raise(mrb, E_NOTIMP_ERROR, "socket is not supported on this platform");
#else
#ifndef S_ISSOCK
+2 -2
View File
@@ -17,7 +17,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
#include <winsock.h>
#include <io.h>
#include <basetsd.h>
@@ -783,7 +783,7 @@ fptr_finalize(mrb_state *mrb, struct mrb_io *fptr, int quiet)
#ifndef MRB_NO_IO_POPEN
if (fptr->pid != 0) {
#if !defined(_WIN32) && !defined(_WIN64)
#if !defined(_WIN32)
pid_t pid;
int status;
do {
+5 -5
View File
@@ -3,7 +3,7 @@
#include <errno.h>
#include <string.h>
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
#include <winsock.h>
#include <io.h>
@@ -79,7 +79,7 @@ mrb_io_test_io_setup(mrb_state *mrb, mrb_value self)
mode_t mask;
FILE *fp;
int i;
#if !defined(_WIN32) && !defined(_WIN64)
#if !defined(_WIN32)
struct sockaddr_un sun0 = { 0 }; /* Initialize them all because it is environment dependent */
#endif
@@ -88,7 +88,7 @@ mrb_io_test_io_setup(mrb_state *mrb, mrb_value self)
mask = umask(077);
for (i = 0; i < IDX_COUNT; i++) {
mrb_value fname = mrb_str_new_capa(mrb, 0);
#if !defined(_WIN32) && !defined(_WIN64)
#if !defined(_WIN32)
/*
* Workaround for not being able to bind a socket to some file systems
* (e.g. vboxsf, NFS). [#4981]
@@ -129,7 +129,7 @@ mrb_io_test_io_setup(mrb_state *mrb, mrb_value self)
}
fclose(fp);
#if !defined(_WIN32) && !defined(_WIN64)
#if !defined(_WIN32)
unlink(fnames[IDX_LINK]);
if (symlink(basename(fnames[IDX_READ]), fnames[IDX_LINK]) == -1) {
mrb_raise(mrb, E_RUNTIME_ERROR, "can't make a symbolic link");
@@ -214,7 +214,7 @@ mrb_io_test_rmdir(mrb_state *mrb, mrb_value klass)
static mrb_value
mrb_io_win_p(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
# if defined(__CYGWIN__) || defined(__CYGWIN32__)
return mrb_false_value();
# else
+1 -1
View File
@@ -3,7 +3,7 @@
#include <mruby.h>
#include <mruby/error.h>
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN32)
#include <io.h>
+1 -1
View File
@@ -1721,7 +1721,7 @@ RETRY_TRY_BLOCK:
if (irep->clen > 0 &&
(ch = catch_handler_find(irep, ci->pc, MRB_CATCH_FILTER_ENSURE))) {
/* avoiding a jump from a catch handler into the same handler */
if (a < mrb_irep_catch_handler_unpack(ch->begin) || a >= mrb_irep_catch_handler_unpack(ch->end)) {
if (a < mrb_irep_catch_handler_unpack(ch->begin) || a > mrb_irep_catch_handler_unpack(ch->end)) {
THROW_TAGGED_BREAK(mrb, RBREAK_TAG_JUMP, mrb->c->ci, mrb_fixnum_value(a));
}
}
+94
View File
@@ -71,6 +71,63 @@ assert('break', '11.5.2.4.3') do
end
end
assert_equal [1,2,3,4], a
a = []
begin
while true
a.push 1
break
a.push "NG"
end
ensure
a.push 2
end
assert_equal [1, 2], a
a = []
begin
while true
a.push 1
break
end
a.push 2
ensure
a.push 3
end
assert_equal [1, 2, 3], a
a = []
begin
while true
begin
a.push 1
break
ensure
a.push 2
end
a.push "NG"
end
ensure
a.push 3
end
assert_equal [1, 2, 3], a
a = []
begin
while true
begin
a.push 1
break
ensure
a.push 2
end
a.push "NG"
end
a.push 3
ensure
a.push 4
end
assert_equal [1, 2, 3, 4], a
end
assert('redo', '11.5.2.4.5') do
@@ -95,6 +152,43 @@ assert('redo', '11.5.2.4.5') do
a.push(n)
end
assert_equal [1,3,4], a
a = []
limit = 3
e = RuntimeError.new("!")
for i in 0...3
begin
limit -= 1
break unless limit > 0
a.push i * 3 + 1
raise e
rescue
a.push i * 3 + 2
redo
ensure
a.push i * 3 + 3
end
end
assert_equal [1, 2, 3, 1, 2, 3, 3], a
a = []
limit = 3
e = RuntimeError.new("!")
for i in 0...3
a.push i * 4 + 1
begin
limit -= 1
break unless limit > 0
a.push i * 4 + 2
raise e
rescue
a.push i * 4 + 3
redo
ensure
a.push i * 4 + 4
end
end
assert_equal [1, 2, 3, 4, 1, 2, 3, 4, 1, 4], a
end
assert('Abbreviated variable assignment', '11.4.2.3.2') do