42 | %% 43 | %% The testing process begins by defining a plan using etap:plan/1, running 44 | %% a number of etap tests and then calling eta:end_tests/0. Please refer to 45 | %% the Erlang modules in the t directory of this project for example tests. 46 | -module(etap). 47 | -vsn("0.3.4"). 48 | 49 | -export([ 50 | ensure_test_server/0, 51 | start_etap_server/0, 52 | test_server/1, 53 | msg/1, msg/2, 54 | diag/1, diag/2, 55 | expectation_mismatch_message/3, 56 | plan/1, 57 | end_tests/0, 58 | not_ok/2, ok/2, is_ok/2, is/3, isnt/3, any/3, none/3, 59 | fun_is/3, expect_fun/3, expect_fun/4, 60 | is_greater/3, 61 | skip/1, skip/2, 62 | datetime/1, 63 | skip/3, 64 | bail/0, bail/1, 65 | test_state/0, failure_count/0 66 | ]). 67 | 68 | -export([ 69 | contains_ok/3, 70 | is_before/4 71 | ]). 72 | 73 | -export([ 74 | is_pid/2, 75 | is_alive/2, 76 | is_mfa/3 77 | ]). 78 | 79 | -export([ 80 | loaded_ok/2, 81 | can_ok/2, can_ok/3, 82 | has_attrib/2, is_attrib/3, 83 | is_behaviour/2 84 | ]). 85 | 86 | -export([ 87 | dies_ok/2, 88 | lives_ok/2, 89 | throws_ok/3 90 | ]). 91 | 92 | 93 | -record(test_state, { 94 | planned = 0, 95 | count = 0, 96 | pass = 0, 97 | fail = 0, 98 | skip = 0, 99 | skip_reason = "" 100 | }). 101 | 102 | %% @spec plan(N) -> Result 103 | %% N = unknown | skip | {skip, string()} | integer() 104 | %% Result = ok 105 | %% @doc Create a test plan and boot strap the test server. 106 | plan(unknown) -> 107 | %ensure_coverage_starts(), 108 | ensure_test_server(), 109 | etap_server ! {self(), plan, unknown}, 110 | ok; 111 | plan(skip) -> 112 | io:format("1..0 # skip~n"); 113 | plan({skip, Reason}) -> 114 | io:format("1..0 # skip ~s~n", [Reason]); 115 | plan(N) when is_integer(N), N > 0 -> 116 | %ensure_coverage_starts(), 117 | ensure_test_server(), 118 | etap_server ! {self(), plan, N}, 119 | ok. 120 | 121 | %% @spec end_tests() -> ok 122 | %% @doc End the current test plan and output test results. 123 | %% @todo This should probably be done in the test_server process. 124 | end_tests() -> 125 | %ensure_coverage_ends(), 126 | case whereis(etap_server) of 127 | undefined -> self() ! true; 128 | _ -> etap_server ! {self(), state} 129 | end, 130 | State = receive {etap, X} -> X end, 131 | if 132 | State#test_state.planned == -1 -> 133 | io:format("1..~p~n", [State#test_state.count]); 134 | true -> 135 | ok 136 | end, 137 | case whereis(etap_server) of 138 | undefined -> ok; 139 | _ -> etap_server ! done, ok 140 | end. 141 | 142 | bail() -> 143 | bail(""). 144 | 145 | bail(Reason) -> 146 | etap_server ! {self(), diag, "Bail out! " ++ Reason}, 147 | %ensure_coverage_ends(), 148 | etap_server ! done, ok, 149 | ok. 150 | 151 | %% @spec test_state() -> Return 152 | %% Return = test_state_record() | {error, string()} 153 | %% @doc Return the current test state 154 | test_state() -> 155 | etap_server ! {self(), state}, 156 | receive 157 | {etap, X} when is_record(X, test_state) -> X 158 | after 1000 -> 159 | {error, "Timed out waiting for etap server reply.~n"} 160 | end. 161 | 162 | %% @spec failure_count() -> Return 163 | %% Return = integer() | {error, string()} 164 | %% @doc Return the current failure count 165 | failure_count() -> 166 | case test_state() of 167 | #test_state{fail=FailureCount} -> FailureCount; 168 | X -> X 169 | end. 170 | 171 | %% @spec msg(S) -> ok 172 | %% S = string() 173 | %% @doc Print a message in the test output. 174 | msg(S) -> etap_server ! {self(), diag, S}, ok. 175 | 176 | %% @spec msg(Format, Data) -> ok 177 | %% Format = atom() | string() | binary() 178 | %% Data = [term()] 179 | %% UnicodeList = [Unicode] 180 | %% Unicode = int() 181 | %% @doc Print a message in the test output. 182 | %% Function arguments are passed through io_lib:format/2. 183 | msg(Format, Data) -> msg(io_lib:format(Format, Data)). 184 | 185 | %% @spec diag(S) -> ok 186 | %% S = string() 187 | %% @doc Print a debug/status message related to the test suite. 188 | diag(S) -> msg("# " ++ S). 189 | 190 | %% @spec diag(Format, Data) -> ok 191 | %% Format = atom() | string() | binary() 192 | %% Data = [term()] 193 | %% UnicodeList = [Unicode] 194 | %% Unicode = int() 195 | %% @doc Print a debug/status message related to the test suite. 196 | %% Function arguments are passed through io_lib:format/2. 197 | diag(Format, Data) -> diag(io_lib:format(Format, Data)). 198 | 199 | %% @spec expectation_mismatch_message(Got, Expected, Desc) -> ok 200 | %% Got = any() 201 | %% Expected = any() 202 | %% Desc = string() 203 | %% @doc Print an expectation mismatch message in the test output. 204 | expectation_mismatch_message(Got, Expected, Desc) -> 205 | msg(" ---"), 206 | msg(" description: ~p", [Desc]), 207 | msg(" found: ~p", [Got]), 208 | msg(" wanted: ~p", [Expected]), 209 | msg(" ..."), 210 | ok. 211 | 212 | % @spec evaluate(Pass, Got, Expected, Desc) -> Result 213 | %% Pass = true | false 214 | %% Got = any() 215 | %% Expected = any() 216 | %% Desc = string() 217 | %% Result = true | false 218 | %% @doc Evaluate a test statement, printing an expectation mismatch message 219 | %% if the test failed. 220 | evaluate(Pass, Got, Expected, Desc) -> 221 | case mk_tap(Pass, Desc) of 222 | false -> 223 | expectation_mismatch_message(Got, Expected, Desc), 224 | false; 225 | true -> 226 | true 227 | end. 228 | 229 | %% @spec ok(Expr, Desc) -> Result 230 | %% Expr = true | false 231 | %% Desc = string() 232 | %% Result = true | false 233 | %% @doc Assert that a statement is true. 234 | ok(Expr, Desc) -> evaluate(Expr == true, Expr, true, Desc). 235 | 236 | %% @spec not_ok(Expr, Desc) -> Result 237 | %% Expr = true | false 238 | %% Desc = string() 239 | %% Result = true | false 240 | %% @doc Assert that a statement is false. 241 | not_ok(Expr, Desc) -> evaluate(Expr == false, Expr, false, Desc). 242 | 243 | %% @spec is_ok(Expr, Desc) -> Result 244 | %% Expr = any() 245 | %% Desc = string() 246 | %% Result = true | false 247 | %% @doc Assert that two values are the same. 248 | is_ok(Expr, Desc) -> evaluate(Expr == ok, Expr, ok, Desc). 249 | 250 | %% @spec is(Got, Expected, Desc) -> Result 251 | %% Got = any() 252 | %% Expected = any() 253 | %% Desc = string() 254 | %% Result = true | false 255 | %% @doc Assert that two values are the same. 256 | is(Got, Expected, Desc) -> evaluate(Got == Expected, Got, Expected, Desc). 257 | 258 | %% @spec isnt(Got, Expected, Desc) -> Result 259 | %% Got = any() 260 | %% Expected = any() 261 | %% Desc = string() 262 | %% Result = true | false 263 | %% @doc Assert that two values are not the same. 264 | isnt(Got, Expected, Desc) -> evaluate(Got /= Expected, Got, Expected, Desc). 265 | 266 | %% @spec is_greater(ValueA, ValueB, Desc) -> Result 267 | %% ValueA = number() 268 | %% ValueB = number() 269 | %% Desc = string() 270 | %% Result = true | false 271 | %% @doc Assert that an integer is greater than another. 272 | is_greater(ValueA, ValueB, Desc) when is_integer(ValueA), is_integer(ValueB) -> 273 | mk_tap(ValueA > ValueB, Desc). 274 | 275 | %% @spec any(Got, Items, Desc) -> Result 276 | %% Got = any() 277 | %% Items = [any()] 278 | %% Desc = string() 279 | %% Result = true | false 280 | %% @doc Assert that an item is in a list. 281 | any(Got, Items, Desc) when is_function(Got) -> 282 | is(lists:any(Got, Items), true, Desc); 283 | any(Got, Items, Desc) -> 284 | is(lists:member(Got, Items), true, Desc). 285 | 286 | %% @spec none(Got, Items, Desc) -> Result 287 | %% Got = any() 288 | %% Items = [any()] 289 | %% Desc = string() 290 | %% Result = true | false 291 | %% @doc Assert that an item is not in a list. 292 | none(Got, Items, Desc) when is_function(Got) -> 293 | is(lists:any(Got, Items), false, Desc); 294 | none(Got, Items, Desc) -> 295 | is(lists:member(Got, Items), false, Desc). 296 | 297 | %% @spec fun_is(Fun, Expected, Desc) -> Result 298 | %% Fun = function() 299 | %% Expected = any() 300 | %% Desc = string() 301 | %% Result = true | false 302 | %% @doc Use an anonymous function to assert a pattern match. 303 | fun_is(Fun, Expected, Desc) when is_function(Fun) -> 304 | is(Fun(Expected), true, Desc). 305 | 306 | %% @spec expect_fun(ExpectFun, Got, Desc) -> Result 307 | %% ExpectFun = function() 308 | %% Got = any() 309 | %% Desc = string() 310 | %% Result = true | false 311 | %% @doc Use an anonymous function to assert a pattern match, using actual 312 | %% value as the argument to the function. 313 | expect_fun(ExpectFun, Got, Desc) -> 314 | evaluate(ExpectFun(Got), Got, ExpectFun, Desc). 315 | 316 | %% @spec expect_fun(ExpectFun, Got, Desc, ExpectStr) -> Result 317 | %% ExpectFun = function() 318 | %% Got = any() 319 | %% Desc = string() 320 | %% ExpectStr = string() 321 | %% Result = true | false 322 | %% @doc Use an anonymous function to assert a pattern match, using actual 323 | %% value as the argument to the function. 324 | expect_fun(ExpectFun, Got, Desc, ExpectStr) -> 325 | evaluate(ExpectFun(Got), Got, ExpectStr, Desc). 326 | 327 | %% @equiv skip(TestFun, "") 328 | skip(TestFun) when is_function(TestFun) -> 329 | skip(TestFun, ""). 330 | 331 | %% @spec skip(TestFun, Reason) -> ok 332 | %% TestFun = function() 333 | %% Reason = string() 334 | %% @doc Skip a test. 335 | skip(TestFun, Reason) when is_function(TestFun), is_list(Reason) -> 336 | begin_skip(Reason), 337 | catch TestFun(), 338 | end_skip(), 339 | ok. 340 | 341 | %% @spec skip(Q, TestFun, Reason) -> ok 342 | %% Q = true | false | function() 343 | %% TestFun = function() 344 | %% Reason = string() 345 | %% @doc Skips a test conditionally. The first argument to this function can 346 | %% either be the 'true' or 'false' atoms or a function that returns 'true' or 347 | %% 'false'. 348 | skip(QFun, TestFun, Reason) when is_function(QFun), is_function(TestFun), is_list(Reason) -> 349 | case QFun() of 350 | true -> begin_skip(Reason), TestFun(), end_skip(); 351 | _ -> TestFun() 352 | end, 353 | ok; 354 | 355 | skip(Q, TestFun, Reason) when is_function(TestFun), is_list(Reason), Q == true -> 356 | begin_skip(Reason), 357 | TestFun(), 358 | end_skip(), 359 | ok; 360 | 361 | skip(_, TestFun, Reason) when is_function(TestFun), is_list(Reason) -> 362 | TestFun(), 363 | ok. 364 | 365 | %% @private 366 | begin_skip(Reason) -> 367 | etap_server ! {self(), begin_skip, Reason}. 368 | 369 | %% @private 370 | end_skip() -> 371 | etap_server ! {self(), end_skip}. 372 | 373 | %% @spec contains_ok(string(), string(), string()) -> true | false 374 | %% @doc Assert that a string is contained in another string. 375 | contains_ok(Source, String, Desc) -> 376 | etap:isnt( 377 | string:str(Source, String), 378 | 0, 379 | Desc 380 | ). 381 | 382 | %% @spec is_before(string(), string(), string(), string()) -> true | false 383 | %% @doc Assert that a string comes before another string within a larger body. 384 | is_before(Source, StringA, StringB, Desc) -> 385 | etap:is_greater( 386 | string:str(Source, StringB), 387 | string:str(Source, StringA), 388 | Desc 389 | ). 390 | 391 | %% @doc Assert that a given variable is a pid. 392 | is_pid(Pid, Desc) when is_pid(Pid) -> etap:ok(true, Desc); 393 | is_pid(_, Desc) -> etap:ok(false, Desc). 394 | 395 | %% @doc Assert that a given process/pid is alive. 396 | is_alive(Pid, Desc) -> 397 | etap:ok(erlang:is_process_alive(Pid), Desc). 398 | 399 | %% @doc Assert that the current function of a pid is a given {M, F, A} tuple. 400 | is_mfa(Pid, MFA, Desc) -> 401 | etap:is({current_function, MFA}, erlang:process_info(Pid, current_function), Desc). 402 | 403 | %% @spec loaded_ok(atom(), string()) -> true | false 404 | %% @doc Assert that a module has been loaded successfully. 405 | loaded_ok(M, Desc) when is_atom(M) -> 406 | etap:fun_is(fun({module, _}) -> true; (_) -> false end, code:load_file(M), Desc). 407 | 408 | %% @spec can_ok(atom(), atom()) -> true | false 409 | %% @doc Assert that a module exports a given function. 410 | can_ok(M, F) when is_atom(M), is_atom(F) -> 411 | Matches = [X || {X, _} <- M:module_info(exports), X == F], 412 | etap:ok(Matches > 0, lists:concat([M, " can ", F])). 413 | 414 | %% @spec can_ok(atom(), atom(), integer()) -> true | false 415 | %% @doc Assert that a module exports a given function with a given arity. 416 | can_ok(M, F, A) when is_atom(M); is_atom(F), is_number(A) -> 417 | Matches = [X || X <- M:module_info(exports), X == {F, A}], 418 | etap:ok(Matches > 0, lists:concat([M, " can ", F, "/", A])). 419 | 420 | %% @spec has_attrib(M, A) -> true | false 421 | %% M = atom() 422 | %% A = atom() 423 | %% @doc Asserts that a module has a given attribute. 424 | has_attrib(M, A) when is_atom(M), is_atom(A) -> 425 | etap:isnt( 426 | proplists:get_value(A, M:module_info(attributes), 'asdlkjasdlkads'), 427 | 'asdlkjasdlkads', 428 | lists:concat([M, " has attribute ", A]) 429 | ). 430 | 431 | %% @spec has_attrib(M, A. V) -> true | false 432 | %% M = atom() 433 | %% A = atom() 434 | %% V = any() 435 | %% @doc Asserts that a module has a given attribute with a given value. 436 | is_attrib(M, A, V) when is_atom(M) andalso is_atom(A) -> 437 | etap:is( 438 | proplists:get_value(A, M:module_info(attributes)), 439 | [V], 440 | lists:concat([M, "'s ", A, " is ", V]) 441 | ). 442 | 443 | %% @spec is_behavior(M, B) -> true | false 444 | %% M = atom() 445 | %% B = atom() 446 | %% @doc Asserts that a given module has a specific behavior. 447 | is_behaviour(M, B) when is_atom(M) andalso is_atom(B) -> 448 | is_attrib(M, behaviour, B). 449 | 450 | %% @doc Assert that an exception is raised when running a given function. 451 | dies_ok(F, Desc) -> 452 | case (catch F()) of 453 | {'EXIT', _} -> etap:ok(true, Desc); 454 | _ -> etap:ok(false, Desc) 455 | end. 456 | 457 | %% @doc Assert that an exception is not raised when running a given function. 458 | lives_ok(F, Desc) -> 459 | etap:is(try_this(F), success, Desc). 460 | 461 | %% @doc Assert that the exception thrown by a function matches the given exception. 462 | throws_ok(F, Exception, Desc) -> 463 | try F() of 464 | _ -> etap:ok(nok, Desc) 465 | catch 466 | _:E -> 467 | etap:is(E, Exception, Desc) 468 | end. 469 | 470 | %% @private 471 | %% @doc Run a function and catch any exceptions. 472 | try_this(F) when is_function(F, 0) -> 473 | try F() of 474 | _ -> success 475 | catch 476 | throw:E -> {throw, E}; 477 | error:E -> {error, E}; 478 | exit:E -> {exit, E} 479 | end. 480 | 481 | %% @private 482 | %% @doc Start the etap_server process if it is not running already. 483 | ensure_test_server() -> 484 | case whereis(etap_server) of 485 | undefined -> 486 | proc_lib:start(?MODULE, start_etap_server,[]); 487 | _ -> 488 | diag("The test server is already running.") 489 | end. 490 | 491 | %% @private 492 | %% @doc Start the etap_server loop and register itself as the etap_server 493 | %% process. 494 | start_etap_server() -> 495 | catch register(etap_server, self()), 496 | proc_lib:init_ack(ok), 497 | etap:test_server(#test_state{ 498 | planned = 0, 499 | count = 0, 500 | pass = 0, 501 | fail = 0, 502 | skip = 0, 503 | skip_reason = "" 504 | }). 505 | 506 | 507 | %% @private 508 | %% @doc The main etap_server receive/run loop. The etap_server receive loop 509 | %% responds to seven messages apperatining to failure or passing of tests. 510 | %% It is also used to initiate the testing process with the {_, plan, _} 511 | %% message that clears the current test state. 512 | test_server(State) -> 513 | NewState = receive 514 | {_From, plan, unknown} -> 515 | io:format("# Current time local ~s~n", [datetime(erlang:localtime())]), 516 | io:format("# Using etap version ~p~n", [ proplists:get_value(vsn, proplists:get_value(attributes, etap:module_info())) ]), 517 | State#test_state{ 518 | planned = -1, 519 | count = 0, 520 | pass = 0, 521 | fail = 0, 522 | skip = 0, 523 | skip_reason = "" 524 | }; 525 | {_From, plan, N} -> 526 | io:format("# Current time local ~s~n", [datetime(erlang:localtime())]), 527 | io:format("# Using etap version ~p~n", [ proplists:get_value(vsn, proplists:get_value(attributes, etap:module_info())) ]), 528 | io:format("1..~p~n", [N]), 529 | State#test_state{ 530 | planned = N, 531 | count = 0, 532 | pass = 0, 533 | fail = 0, 534 | skip = 0, 535 | skip_reason = "" 536 | }; 537 | {_From, begin_skip, Reason} -> 538 | State#test_state{ 539 | skip = 1, 540 | skip_reason = Reason 541 | }; 542 | {_From, end_skip} -> 543 | State#test_state{ 544 | skip = 0, 545 | skip_reason = "" 546 | }; 547 | {_From, pass, Desc} -> 548 | FullMessage = skip_diag( 549 | " - " ++ Desc, 550 | State#test_state.skip, 551 | State#test_state.skip_reason 552 | ), 553 | io:format("ok ~p ~s~n", [State#test_state.count + 1, FullMessage]), 554 | State#test_state{ 555 | count = State#test_state.count + 1, 556 | pass = State#test_state.pass + 1 557 | }; 558 | 559 | {_From, fail, Desc} -> 560 | FullMessage = skip_diag( 561 | " - " ++ Desc, 562 | State#test_state.skip, 563 | State#test_state.skip_reason 564 | ), 565 | io:format("not ok ~p ~s~n", [State#test_state.count + 1, FullMessage]), 566 | State#test_state{ 567 | count = State#test_state.count + 1, 568 | fail = State#test_state.fail + 1 569 | }; 570 | {From, state} -> 571 | From ! {etap, State}, 572 | State; 573 | {_From, diag, Message} -> 574 | io:format("~s~n", [Message]), 575 | State; 576 | {From, count} -> 577 | From ! {etap, State#test_state.count}, 578 | State; 579 | {From, is_skip} -> 580 | From ! {etap, State#test_state.skip}, 581 | State; 582 | done -> 583 | exit(normal) 584 | end, 585 | test_server(NewState). 586 | 587 | %% @private 588 | %% @doc Process the result of a test and send it to the etap_server process. 589 | mk_tap(Result, Desc) -> 590 | etap_server ! {self(), is_skip}, 591 | IsSkip = receive 592 | {etap, Response} -> Response 593 | end, 594 | case [IsSkip, Result] of 595 | [_, true] -> 596 | etap_server ! {self(), pass, Desc}, 597 | true; 598 | [1, _] -> 599 | etap_server ! {self(), pass, Desc}, 600 | true; 601 | _ -> 602 | etap_server ! {self, fail, Desc}, 603 | false 604 | end. 605 | 606 | %% @private 607 | %% @doc Format a date/time string. 608 | datetime(DateTime) -> 609 | {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime, 610 | io_lib:format("~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B", [Year, Month, Day, Hour, Min, Sec]). 611 | 612 | %% @private 613 | %% @doc Craft an output message taking skip/todo into consideration. 614 | skip_diag(Message, 0, _) -> 615 | Message; 616 | skip_diag(_Message, 1, "") -> 617 | " # SKIP"; 618 | skip_diag(_Message, 1, Reason) -> 619 | " # SKIP : " ++ Reason. 620 | -------------------------------------------------------------------------------- /src/overview.edoc: -------------------------------------------------------------------------------- 1 | 2 | @title A TAP testing client. 3 | @author Nick Gerakines37 | %% TAP, the Test Anything Protocol, is a simple text-based interface between 38 | %% testing modules in a test harness. TAP started life as part of the test 39 | %% harness for Perl but now has implementations in C/C++, Python, PHP, Perl 40 | %% and probably others by the time you read this. 41 | %%
etap is a collection of Erlang modules that provide a TAP testing client library. These modules allow developers to create extensive and comprehensive tests covering many aspects of application and module development. This includes simple assertions, exceptions, the application behavior and event web requests. This library was originally written by Jeremy wall.
9 |As per the TAP wiki:
10 |TAP, the Test Anything Protocol, is a simple text-based interface between testing modules in a test harness. TAP started life as part of the test harness for Perl but now has implementations in C/C++, Python, PHP, Perl and probably others by the time you read this.11 |
These modules are not meant to compete with eunit, but to offer a more general testing facility that isn't provides by eunit.
12 |http://en.wikipedia.org/wiki/Test_Anything_Protocol
13 | http://testanything.org
14 |
15 |
16 | A test is any number of etap:* or etap_*:* tests that are part of a test plan. When a plan is created using etap:plan/1, a process is started that tracks the status of the tests executed and handles diagnostic output.
19 |Consider the following example test plan:
20 |etap:plan(3),
21 | etap:ok(true, "the 'true' atom is recognized"),
22 | etap:is(1 + 1, 2, "simple math"),
23 | etap:isnt(2 + 2, 5, "some would argue"),
24 | etap:end_tests().
25 |
26 | At this time, etap does not support pattern matching. To work around this there are a number of utility tests that can be used. The etap:any/3, etap:none/3 and etap:fun_is/3 use functions to return either true
or false
.
Numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9],
28 | FunWithNumbers = fun(X) case X of [1, 2, 3 | _] -> true; _ -> false end end,
29 | etap:fun_is(FunWithNumbers, Numbers, "Match the first three numbers").
30 |
31 |
32 | There are many examples in t/*.erl.
33 | 34 |To build this library, from the root directory execute the make
command. You should also execute the make test
command to verify that the library functions correctly on your system. If you have the Perl module TAP::Harness you can use it to collect and display test results using the make prove
target.
$ make
39 | $ make test
40 | $ make prove
41 |
42 |
43 | If you choose to run the make test
command then please be sure to make clean
after to remove any of the temporary beam files created by the tests in the t/
directory.
The included tests cover the basic functionality of the etap modules. They can also be used as a reference when writing your own tests.
46 | 47 |To install etap you need to create the etap/ebin/
directory in your current Erlang library and copy all of the .beam files created by the make
file.
$ sudo mkdir -p /usr/lib/erlang/lib/etap-0.3.4/ebin
50 | $ make clean && make
51 | $ sudo cp ebin/*.beam /usr/lib/erlang/lib/etap-0.3.4/ebin/
52 |
53 |
54 | The make dist-src
target can be used to create source distributions for further packaging and deployment.
There are a number of proposals listed on the TAP wiki that are not supported by this library. Please be aware of this when creating your tests.
59 | 60 |2008 Nick Gerakines
74 | 2007-2008 Jeremy Wall
75 | 2008 Jacob Vorreuter