/* ``Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * The Initial Developer of the Original Code is Ericsson Utvecklings AB. * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings * AB. All Rights Reserved.'' * * $Id$ */ #include "testcase_driver.h" #include #include #include #include #include #include #ifdef __WIN32__ static void my_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) { _vsnprintf(outBuf, size, format, ap); outBuf[size-1] = 0; /* be sure string is terminated */ } #elif defined(HAVE_VSNPRINTF) # define my_vsnprintf(B,S,F,A) (void)vsnprintf(B,S,F,A) #else # warning Using unsafe 'vsprintf' without buffer overflow protection # define my_vsnprintf(B,S,F,A) (void)vsprintf(B,F,A) #endif static void my_snprintf(char *outBuf, size_t size, const char *format, ...) { va_list ap; va_start(ap, format); my_vsnprintf(outBuf, size, format, ap); va_end(ap); } #define COMMENT_BUF_SZ 4096 #define TESTCASE_FAILED 0 #define TESTCASE_SKIPPED 1 #define TESTCASE_SUCCEEDED 2 #define TESTCASE_CONTINUE 3 typedef struct { TestCaseState_t visible; int result; jmp_buf* done_jmp_buf; char *comment; char comment_buf[COMMENT_BUF_SZ]; } InternalTestCaseState_t; ERL_NIF_TERM testcase_nif_start(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM testcase_nif_stop(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM testcase_nif_run(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ErlNifFunc testcase_nif_funcs[] = { {"start", 1, testcase_nif_start}, {"run", 1, testcase_nif_run}, {"stop", 1, testcase_nif_stop} }; static ErlNifResourceType* testcase_rt; static ERL_NIF_TERM print_atom; int testcase_nif_init(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { testcase_rt = enif_open_resource_type(env, NULL, "testcase_rt", NULL, ERL_NIF_RT_CREATE, NULL); print_atom = enif_make_atom(env, "print"); return 0; } ERL_NIF_TERM testcase_nif_start(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { /* (ThrNr, FreeMeg, BuildType) */ ERL_NIF_TERM ret; InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) enif_alloc_resource(testcase_rt, sizeof(InternalTestCaseState_t)); int free_megabyte; const int max_megabyte = INT_MAX / (1024*1024); const ERL_NIF_TERM* tpl; int tpl_arity; if (!itcs || !enif_get_tuple(env, argv[0], &tpl_arity, &tpl) || tpl_arity != 3 || !enif_get_int(env, tpl[0], &itcs->visible.thr_nr) || !enif_get_int(env, tpl[1], &free_megabyte)) { enif_make_badarg(env); } itcs->visible.free_mem = (free_megabyte < max_megabyte ? free_megabyte : max_megabyte) * (1024*1024); itcs->visible.testcase_name = testcase_name(); itcs->visible.build_type = tpl[2]; itcs->visible.extra = NULL; itcs->result = TESTCASE_FAILED; itcs->comment = ""; ret = enif_make_resource(env, itcs); enif_release_resource(itcs); return ret; } ERL_NIF_TERM testcase_nif_stop(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { InternalTestCaseState_t *itcs; if (!enif_get_resource(env, argv[0], testcase_rt, (void**)&itcs)) return enif_make_badarg(env); testcase_cleanup(&itcs->visible); return enif_make_atom(env,"ok"); } ERL_NIF_TERM testcase_nif_run(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { InternalTestCaseState_t *itcs; const char* result_atom; jmp_buf the_jmp_buf; if (!enif_get_resource(env, argv[0], testcase_rt, (void**)&itcs)) return enif_make_badarg(env); itcs->visible.curr_env = env; /* For some unknown reason, first call to setjmp crashes on win64 * when jmp_buf is allocated as part of the resource. But it works when * allocated on stack. It used to work when this was a driver. */ itcs->done_jmp_buf = &the_jmp_buf; if (setjmp(the_jmp_buf) == 0) { testcase_run(&itcs->visible); itcs->result = TESTCASE_SUCCEEDED; } switch (itcs->result) { case TESTCASE_CONTINUE: return enif_make_atom(env, "continue"); case TESTCASE_SUCCEEDED: result_atom = "succeeded"; break; case TESTCASE_SKIPPED: result_atom = "skipped"; break; case TESTCASE_FAILED: result_atom = "failed"; break; default: result_atom = "failed"; my_snprintf(itcs->comment_buf, sizeof(itcs->comment_buf), "Unexpected test result code %d.", itcs->result); itcs->comment = itcs->comment_buf; } return enif_make_tuple2(env, enif_make_atom(env, result_atom), enif_make_string(env, itcs->comment, ERL_NIF_LATIN1)); } int testcase_assertion_failed(TestCaseState_t *tcs, char *file, int line, char *assertion) { testcase_failed(tcs, "%s:%d: Assertion failed: \"%s\"", file, line, assertion); return 0; } void testcase_printf(TestCaseState_t *tcs, char *frmt, ...) { InternalTestCaseState_t* itcs = (InternalTestCaseState_t*)tcs; ErlNifPid pid; ErlNifEnv* msg_env = enif_alloc_env(); ERL_NIF_TERM msg; va_list va; va_start(va, frmt); my_vsnprintf(itcs->comment_buf, COMMENT_BUF_SZ, frmt, va); va_end(va); msg = enif_make_tuple2(msg_env, print_atom, enif_make_string(msg_env, itcs->comment_buf, ERL_NIF_LATIN1)); enif_send(itcs->visible.curr_env, enif_self(itcs->visible.curr_env, &pid), msg_env, msg); enif_free_env(msg_env); } void testcase_succeeded(TestCaseState_t *tcs, char *frmt, ...) { InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) tcs; va_list va; va_start(va, frmt); my_vsnprintf(itcs->comment_buf, COMMENT_BUF_SZ, frmt, va); va_end(va); itcs->result = TESTCASE_SUCCEEDED; itcs->comment = itcs->comment_buf; longjmp(*itcs->done_jmp_buf, 1); } void testcase_skipped(TestCaseState_t *tcs, char *frmt, ...) { InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) tcs; va_list va; va_start(va, frmt); my_vsnprintf(itcs->comment_buf, COMMENT_BUF_SZ, frmt, va); va_end(va); itcs->result = TESTCASE_SKIPPED; itcs->comment = itcs->comment_buf; longjmp(*itcs->done_jmp_buf, 1); } void testcase_continue(TestCaseState_t *tcs) { InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) tcs; itcs->result = TESTCASE_CONTINUE; longjmp(*itcs->done_jmp_buf, 1); } void testcase_failed(TestCaseState_t *tcs, char *frmt, ...) { InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) tcs; char buf[10]; size_t bufsz = sizeof(buf); va_list va; va_start(va, frmt); my_vsnprintf(itcs->comment_buf, COMMENT_BUF_SZ, frmt, va); va_end(va); itcs->result = TESTCASE_FAILED; itcs->comment = itcs->comment_buf; if (enif_getenv("ERL_ABORT_ON_FAILURE", buf, &bufsz) == 0 && strcmp("true", buf) == 0) { fprintf(stderr, "Testcase \"%s\" failed: %s\n", itcs->visible.testcase_name, itcs->comment); abort(); } longjmp(*itcs->done_jmp_buf, 1); } void *testcase_alloc(size_t size) { return enif_alloc(size); } void *testcase_realloc(void *ptr, size_t size) { return enif_realloc(ptr, size); } void testcase_free(void *ptr) { enif_free(ptr); }