Permalink
Browse files

Initial import

  • Loading branch information...
0 parents commit 21fe120828c26705a177a6c4ecc7bc8c0be5e2ce @davisp committed Apr 4, 2012
Showing with 1,405 additions and 0 deletions.
  1. +88 −0 LICENSE
  2. +31 −0 Makefile
  3. +3 −0 README.md
  4. +260 −0 c_src/hash.c
  5. +25 −0 c_src/hash.h
  6. +26 −0 c_src/hash_funcs.c
  7. +40 −0 c_src/item.c
  8. +160 −0 c_src/johnny.c
  9. +39 −0 c_src/johnny.h
  10. BIN rebar
  11. +6 −0 src/johnny.app.src
  12. +59 −0 src/johnny.erl
  13. +12 −0 test/001-load.t
  14. +612 −0 test/etap.erl
  15. +44 −0 test/util.erl
88 LICENSE
@@ -0,0 +1,88 @@
+Johnny
+======
+
+Copyright 2012 Paul J. Davis <paul.joseph.davis@gmail.com>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+Etap
+====
+
+Files: test/etap.erl
+
+Copyright (c) 2008-2009 Nick Gerakines <nick@gerakines.net>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+Yajl Test Cases
+===============
+
+Files: test/cases/*.json
+
+Copyright 2010, Lloyd Hilaiel.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ 3. Neither the name of Lloyd Hilaiel nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
31 Makefile
@@ -0,0 +1,31 @@
+
+all: build
+
+clean:
+ ./rebar clean
+ rm -rf logs
+ rm -rf .eunit
+ rm test/*.beam
+
+deps: ./deps/
+ ./rebar get-deps update-deps
+
+
+build: deps
+ ./rebar compile
+
+
+etap: test/etap.beam test/util.beam
+ prove test/*.t
+
+
+eunit:
+ ./rebar eunit skip_deps=true
+
+
+check: etap eunit
+
+
+%.beam: %.erl
+ erlc -o test/ $<
+
3 README.md
@@ -0,0 +1,3 @@
+Logo:
+
+http://www.silberstudios.tv/wp-content/uploads/2011/03/johnny-cash-finger.jpg
260 c_src/hash.c
@@ -0,0 +1,260 @@
+// This file is part of Johnny released under the MIT license.
+// See the LICENSE file for more information.
+//
+// First draft of the implementation was cribbed from khash.h which
+// is also released under the MIT license. khash.h can be found here:
+// http://attractivechaos.awardspace.com/khash.h.html
+//
+// The original khash.h is:
+//
+// Copyright (c) 2008, by Attractive Chaos <attractivechaos@aol.co.uk>
+
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define JOHNNY_HASH_INIT_BUCKETS 1024
+#define JOHNNY_HASH_MAX_LOAD 0.75
+#define JOHNNY_HAHS_MIN_LOAD 0.25
+
+typedef struct _johnny_hash_bucket_t {
+ unsigned int hval;
+ johnny_item_t* item;
+ struct _johnny_hash_bucket_t* next;
+ struct _johnny_hash_bucket_t* tail;
+} johnny_hash_bucket;
+
+typedef struct _johnny_hash_t {
+ johnny_hash_func_t hashfunc;
+ johnny_hash_bucket_t** buckets;
+ int num_buckets;
+ int num_occupied;
+ int upper_bound;
+ int size;
+} johnny_hash_t;
+
+johnny_hash_t*
+johnny_hash_create(ENTERM opts)
+{
+ johnny_hash_t* ret;
+ size_t bucket_bytes;
+
+ ret = (johnny_hash_t*) malloc(sizeof(johnny_hash_t));
+ if(!ret) return NULL;
+
+ bucket_bytes = sizeof(johnny_hash_t*) * JOHNNY_HASH_INIT_BUCKETS;
+
+ ret->hashfunc = &johnny_jenkins_single;
+ ret->buckets = (johnny_bucket_t**) malloc(bucket_bytes);
+ if(!ret->buckets) {
+ free(ret);
+ return NULL;
+ }
+ memset(ret->buckets, 0, bucket_bytes);
+
+ ret->num_buckets = JOHNNY_HASH_INIT_BUCKETS;
+ ret->num_occupied = 0;
+ ret->upper_bound = int(ret->num_buckets * JOHNNY_HASH_MAX_LOAD);
+ ret->size = 0;
+}
+
+void
+johnny_hash_destroy(johnny_hash_t* h)
+{
+ if(!h) return;
+ johnny_hash_clear(h);
+ free(h);
+}
+
+void
+johnny_hash_clear(johnny_hash_t* h)
+{
+ johnny_hash_bucket_t* iter;
+ johnny_hash_bucket_t* next;
+ size_t i;
+
+ assert(h && "hash is not null");
+ assert(h->buckets && "hash has buckets");
+
+ for(i = 0; i < h->num_buckets; i++) {
+ iter = h->buckets[i];
+ while(iter) {
+ next = iter->next;
+ johnny_item_destroy(iter->item);
+ free(iter);
+ iter = next;
+ }
+ }
+ h->num_occupied = 0;
+ h->size = 0;
+}
+
+int
+johnny_hash_get(johnny_hash_t* h, johnny_item_t* item)
+{
+ johnny_hash_bucket_t* iter;
+ unsigned int hval;
+
+ assert(h != NULL && "hash is null");
+ assert(h->buckets != NULL && "hash has no buckets");
+
+ if(!h->hashfunc(item->env, item->key, &hval))
+ return 0;
+
+ iter = h->buckets[hval % h->num_buckets];
+ while(iter) {
+ if(iter->hval != hval) {
+ iter = iter->next;
+ continue;
+ }
+ if(enif_compare(item->key, iter->item->key) == 0) {
+ item->val = enif_make_copy(item->env, iter->item->val);
+ return 1;
+ }
+ iter = iter->next;
+ }
+
+ return 0;
+}
+
+int
+johnny_hash_put(johnny_hash_t* h, johnny_item_t* item)
+{
+ johnny_hash_bucket_t* iter;
+ johnny_hash_bucket_t* next;
+ unsigned int hval;
+
+ assert(h != NULL && "hash is null");
+ assert(h->buckset != NULL && "hash has no buckets");
+
+ if(!h->hashfunc(item->env, item->key, &hval))
+ return 0;
+
+ iter = h->buckets[hval % h->num_buckets];
+ while(iter) {
+ if(iter->hval != hval) {
+ iter = iter->next;
+ continue;
+ }
+ if(enif_compare(item->key, iter->item->key) == 0) {
+ johnny_item_destroy(iter->item);
+ iter->item = item;
+ return 1;
+ }
+ iter = iter->next;
+ }
+
+ next = (johnny_hash_bucket_t*) malloc(sizeof(johnny_hash_bucket_t));
+ if(!next) return 0;
+ next->hval = hval;
+ next->item = item;
+ next->next = NULL;
+ next->tail = NULL;
+
+ h->buckets[hval % h->num_buckets]->tail->next = next;
+ h->buckets[hval % h->num_buckets]->tail = next;
+
+ return 1;
+}
+
+int
+johnny_hash_del(johnny_hash_t* h, johnny_item_t* item)
+{
+ johnny_hash_bucket_t* iter;
+ johnny_hash_bucket_t* prev;
+ unsigned int hval;
+
+ assert(h != NULL && "hash is null");
+ assert(h->buckets != NULL && "hash has no buckets");
+
+ if(!h->hashfunc(i->env, i->key, &hval))
+ return 0;
+
+ iter = h->buckets[hval % h->num_buckets];
+ prev = NULL;
+ while(iter) {
+ if(iter->hval != hval) {
+ prev = iter;
+ iter = iter->next;
+ continue;
+ }
+ if(enif_compare(item->key, iter->item->key) == 0) {
+ break;
+ }
+ prev = iter;
+ iter = iter->next;
+ }
+
+ if(!iter) return 0;
+
+ if(prev == NULL) {
+ h->buckets[hval % h->num_buckets] = iter->next;
+ iter->next->tail = iter->tail;
+ } else {
+ prev->next = iter->next;
+ }
+
+ item->val = enif_make_copy(item->env, iter->item->val);
+ johnny_item_destroy(iter->item);
+ free(iter);
+ return 0;
+}
+
+int
+johnny_hash_size(johnny_hash_t* h)
+{
+ return h->size;
+}
+
+int
+johnny_hash_resize(johnny_hash_t* h)
+{
+ johnny_hash_bucket_t* new_buckets;
+ johnny_hash_bucket_t* iter;
+ johnny_hash_bucket_t* next;
+ unsigned int new_size;
+ unsigned int new_occuppied;
+ size_t bucket_bytes;
+ size_t i;
+
+ if(h->num_occupied < h->upper_bound) return 1;
+
+ new_num_buckets = int(h->num_occupied / JOHNNY_HASH_MIN_LOAD);
+ new_occupied = 0;
+ new_size = 0;
+
+ bucket_bytes = sizeof(johnny_hash_bucket_t*) * new_num_buckets;
+ new_buckets = (johnny_hash_bucket_t*) malloc(bucket_bytes);
+ if(new_buckets == NULL)
+ return 0;
+
+ for(i = 0; i < h->num_buckets; i++) {
+ while(h->buckets[i]) {
+ new_size++;
+ next = h->buckets[i];
+ h->buckets[i] = next->next;
+ next->next = NULL;
+ next->tail = NULL;
+ iter = new_buckets[next->hval % new_size];
+ if(iter == NULL) {
+ new_buckets[next->hval % new_size] = next;
+ new_occupied++;
+ continue;
+ }
+ iter->tail->next = next;
+ iter->tail = next;
+ }
+ }
+
+ free(h->buckets);
+ h->buckets = new_buckets;
+ h->num_buckets = new_size;
+ h->num_occupied = new_occupied
+ assert(new_size == h->size && "missing item after hash resize");
+ h->upper_bound = int(JOHNNY_HASH_MAX_LOAD * num_buckets);
+
+ return 1;
+}
+
25 c_src/hash.h
@@ -0,0 +1,25 @@
+// This file is part of Johnny released under the MIT license.
+// See the LICENSE file for more information.
+
+#ifndef JOHNNY_HASH_H
+#define JOHNNY_HASH_H
+
+#include "johnny.h"
+
+typedef struct _johnny_hash_bucket_t johnny_hash_bucket;
+typedef struct _johnny_hash_t johnny_hash_t;
+typedef int (johnny_hash_func_t*) (ErlNifEnv*, ENTERM, unsigned int*);
+
+johnny_hash_t* johnny_hash_create(ENTERM opts);
+void johnny_hash_destroy(johnny_hash_t* h);
+void johnny_hash_clear(johnny_hash_t* h);
+int johnny_hash_get(johnny_hash_t* h, johnny_item_t* i);
+int johnny_hash_put(johnny_hash_t* h, johnny_item_t* i);
+int johnny_hash_del(johnny_hash_t* h, johnny_item_t* i);
+int johnny_hash_size(johnny_hash_t* h);
+int johnny_hash_resize(johnny_hash_t* h);
+
+// Hash Functions
+int johnny_jenkins_single(ErlNifEnv* env, ENTERM key, unsigned int* hash);
+
+#endif // Included hash.h
26 c_src/hash_funcs.c
@@ -0,0 +1,26 @@
+
+int
+johnny_jenkins_single(ErlNifEnv* env, ENTERM key, unsigned int* ret)
+{
+ ErlNifBinary bin;
+ unsigned int hash;
+ unsigned int i;
+
+ *ret = 0;
+
+ if(!enif_inspect_binary(env, key, &bin))
+ return 0;
+
+ for(hash = i = 0; i < bin.size; ++i) {
+ hash += bin.data[i];
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ *ret = hash;
+
+ return 1;
+}
40 c_src/item.c
@@ -0,0 +1,40 @@
+// This file is part of Johnny released under the MIT license.
+// See the LICENSE file for more information.
+
+#include "johnny.h"
+
+johnny_item_t*
+johnny_item_create1(ErlNifEnv* env, ENTERM key)
+{
+ johnny_item_t* ret = (johnny_item_t*) malloc(sizeof(johnny_item_t));
+ if(!ret) return NULL;
+ ret->env = env;
+ ret->key = key;
+ return ret;
+}
+
+johnny_item_t*
+johnny_item_create2(ErlNifEnv* env, ENTERM key, ENTERM val)
+{
+ johnny_item_t* ret = (johnny_item_t*) malloc(sizeof(johnny_item_t));
+ if(!ret) return NULL;
+
+ ret->env = enif_alloc_env();
+ if(!ret->env) {
+ free(ret);
+ return NULL;
+ }
+
+ ret->key = enif_make_copy(ret->env, key);
+ ret->val = enif_make_copy(ret->env, val);
+
+ return ret;
+}
+
+void
+johnny_item_destroy(johnny_item_t* item)
+{
+ if(!item) return;
+ if(item->env) enif_free_env(item->env);
+ free(item);
+}
160 c_src/johnny.c
@@ -0,0 +1,160 @@
+// This file is part of Johnny released under the MIT license.
+// See the LICENSE file for more information.
+
+#include "johnny.h"
+
+ErlNifResourceType* johnny_res;
+
+void
+johnny_res_dtor(void* obj)
+{
+ johnny_res_t* ctx = (johnny_ctx*) obj;
+ ctx->destroy(ctx->data);
+}
+
+static int
+load(ErlNifEnv* env, void** priv, ENTERM info)
+{
+ int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER;
+ johnny_res = enif_open_resource_type(
+ env, NULL, "johnny_res", johnny_dtor, flags, NULL
+ );
+ if(johnny_res == NULL) return 1;
+ return 0;
+}
+
+static int
+reload(ErlNifEnv* env, void** priv, ENTERM info)
+{
+ return 0;
+}
+
+static int
+upgrade(ErlNifEnv* env, void** priv, void** old_priv, ENTERM info)
+{
+ return 0;
+}
+
+static void
+unload(ErlNifEnv* env, void* priv)
+{
+ return;
+}
+
+const ENTERM
+nif_new(ErlNifEnv* env, int argc, const ENTERM argv[])
+{
+ johnny_t* ctx = (johnny_t*) enif_alloc_resource(
+ johnny_res, sizeof(johnny_t)
+ );
+ ENTERM ret;
+
+ if(argc != 1)
+ return enif_make_badarg(env);
+
+ // Single type for now. Eventually iterate the
+ // options and look for a specified type.
+ if(!johnny_hash_init(ctx, argv[0])) {
+ ctx->finalized = 1;
+ enif_release_resource(ctx);
+ return make_atom(env, "init_error");
+ }
+
+ ctx->finalized = 0;
+ ret = enif_make_resource(env, ctx);
+ enif_release_resource(ctx);
+ return ret;
+}
+
+const ENTERM
+nif_get(ErlNifEnv* env, int argc, const ENTERM argv[])
+{
+ johnny_t* ctx;
+ ENTERM ret;
+
+ if(argc != 2)
+ return enif_make_badarg(env);
+ if(!enif_get_resource(env, argv[0], johnny_res, (void**) &ctx))
+ return enif_make_badarg(env);
+
+ if(ctx->finalized)
+ return make_error(env, "already_finalized");
+
+ if(!ctx->get(ctx->data, argv[1], &ret))
+ return make_error(env, "internal_error");
+
+ return ret;
+}
+
+const ENTERM
+nif_put(ErlNifEnv* env, int argc, const ENTERM argv[])
+{
+ johnny_t* ctx;
+ ENTERM ret;
+
+ if(argc != 3)
+ return enif_make_badarg(env);
+ if(!enif_get_resource(env, argv[0], johnny_res, (void**) &ctx))
+ return enif_make_badarg(env);
+
+ if(ctx->finalized)
+ return make_error(env, "already_finalized");
+
+ if(!ctx->put(ctx->data, argv[1], argv[2]))
+ return make_error(env, "internal_error");
+
+ return make_ok(env);
+}
+
+const ENTERM
+nif_del(ErlNifEnv* env, int argc, const ENTERM argv[])
+{
+ johnny_t* ctx;
+ ENTERM ret;
+
+ if(argc != 2)
+ return enif_make_badarg(env);
+ if(!enif_get_resource(env, argv[0], johnny_res, (void**) &ctx))
+ return enif_make_badarg(env);
+
+ if(ctx->finalized)
+ return make_error(env, "already_finalized");
+
+ if(!ctx->del(ctx-data, argv[1], &ret))
+ return make_error(env, "internal_error");
+
+ return johnny_make_ok(ctx, env, ret);
+}
+
+const ENTERM
+nif_size(ErlNifEnv* env, int argc, const ENTERM argv[])
+{
+ johnny_t* ctx;
+ int size;
+
+ if(argc != 1)
+ return enif_make_badarg(env);
+ if(!enif_get_resource(env, argv[0], johnny_res, (void**) &ctx))
+ return enif_make_badarg(env);
+
+ if(ctx->finalized)
+ return make_error("already_finalized");
+
+ size = ctx->size(ctx->data);
+ if(size < 0) {
+ return make_error("internal_error");
+ } else {
+ return johnny_make_ok(ctx, env, enif_make_int(env, size));
+ }
+}
+
+static ErlNifFun funcs[] =
+{
+ {"nif_new", 1, nif_new},
+ {"nif_get", 2, nif_get},
+ {"nif_put", 3, nif_put},
+ {"nif_del", 2, nif_del},
+ {"nif_size", 1, nif_size}
+};
+
+ERL_NIF_INIT(johnny, funcs, &load, &reload, &upgrade, &unload);
39 c_src/johnny.h
@@ -0,0 +1,39 @@
+// This file is part of Johnny released under the MIT license.
+// See the LICENSE file for more information.
+
+#ifndef JOHNNY_H
+#define JOHNNY_H
+
+#include "erl_nif.h"
+
+typedef ERL_NIF_TERM ENTERM;
+
+typedef struct _johnny_item_t
+{
+ ErlNifEnv* env;
+ ENTERM key;
+ ENTERM val;
+} johnny_val_t;
+
+typedef struct _johnny_t
+{
+ void* data;
+ int finalized;
+ int (*get) (johnny_t*, johnny_item_t* item);
+ int (*put) (johnny_t*, johnny_item_t* item);
+ int (*del) (johnny_t*, johnny_item_t* item);
+ int (*size) (johnny_t*);
+ void (*dtor) (johnny_t*);
+} johnny_t;
+
+ERL_NIF_TERM johnny_make_atom(ErlNifEnv* env, const char* name);
+ERL_NIF_TERM johnny_make_ok(johnny_ct* c, ErlNifEnv* env, ERL_NIF_TERM data);
+ERL_NIF_TERM johnny_make_error(johnny_ct* c, ErlNifEnv* env, const char* error);
+
+int johnny_hash_init(johnny_t* ctx, ENTERM opts);
+
+johnny_item_t* johnny_item_create1(ErlNifEnv* env, ENTERM key);
+johnny_item_t* johnny_item_create2(ErlNifEnv* env, ENTERM key, ENTERM val);
+void johnny_item_destroy(johnny_item_t* item);
+
+#endif // Included johnny.h
BIN rebar
Binary file not shown.
6 src/johnny.app.src
@@ -0,0 +1,6 @@
+{application, johnny, [
+ {description, "A NIF for efficient caching of Erlang terms."},
+ {vsn, git},
+ {registered, []},
+ {applications, [kernel]}
+]}.
59 src/johnny.erl
@@ -0,0 +1,59 @@
+% This file is part of Johnny released under the MIT license.
+% See the LICENSE file for more information.
+
+-module(johnny).
+-export([new/0, new/1, get/2, put/3, del/2]).
+-define(NOT_LOADED, not_loaded(?LINE)).
+
+
+-on_load(init/0).
+
+
+new() ->
+ new([]).
+
+
+new(_Options) ->
+ ?NOT_LOADED.
+
+
+get(Cache, Key) when is_binary(Key) ->
+ nif_get(Cache, Key);
+get(Cache, Key) ->
+ nif_get(Cache, term_to_binary(Key)).
+
+
+put(Cache, Key, Val) when is_binary(Key) ->
+ nif_put(Cache, Key, Val);
+put(Cache, Key, Val) ->
+ nif_put(Cache, term_to_binary(Key), Val).
+
+
+del(Cache, Key) when is_binary(Key) ->
+ nif_del(Cache, Key);
+del(Cache, Key) ->
+ nif_del(Cache, term_to_binary(Key)).
+
+
+init() ->
+ PrivDir = case code:priv_dir(?MODULE) of
+ {error, _} ->
+ EbinDir = filename:dirname(code:which(?MODULE)),
+ AppPath = filename:dirname(EbinDir),
+ filename:join(AppPath, "priv");
+ Path ->
+ Path
+ end,
+ erlang:load_nif(filename:join(PrivDir, "johnny"), 0).
+
+
+nif_get(Cache, Key) ->
+ ?NOT_LOADED.
+
+
+nif_put(Cache, Key, Val) ->
+ ?NOT_LOADED.
+
+
+nif_del(Cache, Key) ->
+ ?NOT_LOADED.
12 test/001-load.t
@@ -0,0 +1,12 @@
+#! /usr/bin/env escript
+% This file is part of Johnny released under the MIT license.
+% See the LICENSE file for more information.
+
+main([]) ->
+ code:add_pathz("test"),
+ code:add_pathz("ebin"),
+
+ etap:plan(1),
+ etap:loaded_ok(johnny, "Loaded johnny"),
+ etap:end_tests().
+
612 test/etap.erl
@@ -0,0 +1,612 @@
+%% Copyright (c) 2008-2009 Nick Gerakines <nick@gerakines.net>
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use,
+%% copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following
+%% conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+%% OTHER DEALINGS IN THE SOFTWARE.
+%%
+%% @author Nick Gerakines <nick@gerakines.net> [http://socklabs.com/]
+%% @author Jeremy Wall <jeremy@marzhillstudios.com>
+%% @version 0.3.4
+%% @copyright 2007-2008 Jeremy Wall, 2008-2009 Nick Gerakines
+%% @reference http://testanything.org/wiki/index.php/Main_Page
+%% @reference http://en.wikipedia.org/wiki/Test_Anything_Protocol
+%% @todo Finish implementing the skip directive.
+%% @todo Document the messages handled by this receive loop.
+%% @todo Explain in documentation why we use a process to handle test input.
+%% @doc etap is a TAP testing module for Erlang components and applications.
+%% This module allows developers to test their software using the TAP method.
+%%
+%% <blockquote cite="http://en.wikipedia.org/wiki/Test_Anything_Protocol"><p>
+%% 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.
+%% </p></blockquote>
+%%
+%% The testing process begins by defining a plan using etap:plan/1, running
+%% a number of etap tests and then calling eta:end_tests/0. Please refer to
+%% the Erlang modules in the t directory of this project for example tests.
+-module(etap).
+-vsn("0.3.4").
+
+-export([
+ ensure_test_server/0,
+ start_etap_server/0,
+ test_server/1,
+ msg/1, msg/2,
+ diag/1, diag/2,
+ expectation_mismatch_message/3,
+ plan/1,
+ end_tests/0,
+ not_ok/2, ok/2, is_ok/2, is/3, isnt/3, any/3, none/3,
+ fun_is/3, expect_fun/3, expect_fun/4,
+ is_greater/3,
+ skip/1, skip/2,
+ datetime/1,
+ skip/3,
+ bail/0, bail/1,
+ test_state/0, failure_count/0
+]).
+
+-export([
+ contains_ok/3,
+ is_before/4
+]).
+
+-export([
+ is_pid/2,
+ is_alive/2,
+ is_mfa/3
+]).
+
+-export([
+ loaded_ok/2,
+ can_ok/2, can_ok/3,
+ has_attrib/2, is_attrib/3,
+ is_behaviour/2
+]).
+
+-export([
+ dies_ok/2,
+ lives_ok/2,
+ throws_ok/3
+]).
+
+
+-record(test_state, {
+ planned = 0,
+ count = 0,
+ pass = 0,
+ fail = 0,
+ skip = 0,
+ skip_reason = ""
+}).
+
+%% @spec plan(N) -> Result
+%% N = unknown | skip | {skip, string()} | integer()
+%% Result = ok
+%% @doc Create a test plan and boot strap the test server.
+plan(unknown) ->
+ ensure_test_server(),
+ etap_server ! {self(), plan, unknown},
+ ok;
+plan(skip) ->
+ io:format("1..0 # skip~n");
+plan({skip, Reason}) ->
+ io:format("1..0 # skip ~s~n", [Reason]);
+plan(N) when is_integer(N), N > 0 ->
+ ensure_test_server(),
+ etap_server ! {self(), plan, N},
+ ok.
+
+%% @spec end_tests() -> ok
+%% @doc End the current test plan and output test results.
+%% @todo This should probably be done in the test_server process.
+end_tests() ->
+ case whereis(etap_server) of
+ undefined -> self() ! true;
+ _ -> etap_server ! {self(), state}
+ end,
+ State = receive X -> X end,
+ if
+ State#test_state.planned == -1 ->
+ io:format("1..~p~n", [State#test_state.count]);
+ true ->
+ ok
+ end,
+ case whereis(etap_server) of
+ undefined -> ok;
+ _ -> etap_server ! done, ok
+ end.
+
+bail() ->
+ bail("").
+
+bail(Reason) ->
+ etap_server ! {self(), diag, "Bail out! " ++ Reason},
+ etap_server ! done, ok,
+ ok.
+
+%% @spec test_state() -> Return
+%% Return = test_state_record() | {error, string()}
+%% @doc Return the current test state
+test_state() ->
+ etap_server ! {self(), state},
+ receive
+ X when is_record(X, test_state) -> X
+ after
+ 1000 -> {error, "Timed out waiting for etap server reply.~n"}
+ end.
+
+%% @spec failure_count() -> Return
+%% Return = integer() | {error, string()}
+%% @doc Return the current failure count
+failure_count() ->
+ case test_state() of
+ #test_state{fail=FailureCount} -> FailureCount;
+ X -> X
+ end.
+
+%% @spec msg(S) -> ok
+%% S = string()
+%% @doc Print a message in the test output.
+msg(S) -> etap_server ! {self(), diag, S}, ok.
+
+%% @spec msg(Format, Data) -> ok
+%% Format = atom() | string() | binary()
+%% Data = [term()]
+%% UnicodeList = [Unicode]
+%% Unicode = int()
+%% @doc Print a message in the test output.
+%% Function arguments are passed through io_lib:format/2.
+msg(Format, Data) -> msg(io_lib:format(Format, Data)).
+
+%% @spec diag(S) -> ok
+%% S = string()
+%% @doc Print a debug/status message related to the test suite.
+diag(S) -> msg("# " ++ S).
+
+%% @spec diag(Format, Data) -> ok
+%% Format = atom() | string() | binary()
+%% Data = [term()]
+%% UnicodeList = [Unicode]
+%% Unicode = int()
+%% @doc Print a debug/status message related to the test suite.
+%% Function arguments are passed through io_lib:format/2.
+diag(Format, Data) -> diag(io_lib:format(Format, Data)).
+
+%% @spec expectation_mismatch_message(Got, Expected, Desc) -> ok
+%% Got = any()
+%% Expected = any()
+%% Desc = string()
+%% @doc Print an expectation mismatch message in the test output.
+expectation_mismatch_message(Got, Expected, Desc) ->
+ msg(" ---"),
+ msg(" description: ~p", [Desc]),
+ msg(" found: ~p", [Got]),
+ msg(" wanted: ~p", [Expected]),
+ msg(" ..."),
+ ok.
+
+% @spec evaluate(Pass, Got, Expected, Desc) -> Result
+%% Pass = true | false
+%% Got = any()
+%% Expected = any()
+%% Desc = string()
+%% Result = true | false
+%% @doc Evaluate a test statement, printing an expectation mismatch message
+%% if the test failed.
+evaluate(Pass, Got, Expected, Desc) ->
+ case mk_tap(Pass, Desc) of
+ false ->
+ expectation_mismatch_message(Got, Expected, Desc),
+ false;
+ true ->
+ true
+ end.
+
+%% @spec ok(Expr, Desc) -> Result
+%% Expr = true | false
+%% Desc = string()
+%% Result = true | false
+%% @doc Assert that a statement is true.
+ok(Expr, Desc) -> evaluate(Expr == true, Expr, true, Desc).
+
+%% @spec not_ok(Expr, Desc) -> Result
+%% Expr = true | false
+%% Desc = string()
+%% Result = true | false
+%% @doc Assert that a statement is false.
+not_ok(Expr, Desc) -> evaluate(Expr == false, Expr, false, Desc).
+
+%% @spec is_ok(Expr, Desc) -> Result
+%% Expr = any()
+%% Desc = string()
+%% Result = true | false
+%% @doc Assert that two values are the same.
+is_ok(Expr, Desc) -> evaluate(Expr == ok, Expr, ok, Desc).
+
+%% @spec is(Got, Expected, Desc) -> Result
+%% Got = any()
+%% Expected = any()
+%% Desc = string()
+%% Result = true | false
+%% @doc Assert that two values are the same.
+is(Got, Expected, Desc) -> evaluate(Got == Expected, Got, Expected, Desc).
+
+%% @spec isnt(Got, Expected, Desc) -> Result
+%% Got = any()
+%% Expected = any()
+%% Desc = string()
+%% Result = true | false
+%% @doc Assert that two values are not the same.
+isnt(Got, Expected, Desc) -> evaluate(Got /= Expected, Got, Expected, Desc).
+
+%% @spec is_greater(ValueA, ValueB, Desc) -> Result
+%% ValueA = number()
+%% ValueB = number()
+%% Desc = string()
+%% Result = true | false
+%% @doc Assert that an integer is greater than another.
+is_greater(ValueA, ValueB, Desc) when is_integer(ValueA), is_integer(ValueB) ->
+ mk_tap(ValueA > ValueB, Desc).
+
+%% @spec any(Got, Items, Desc) -> Result
+%% Got = any()
+%% Items = [any()]
+%% Desc = string()
+%% Result = true | false
+%% @doc Assert that an item is in a list.
+any(Got, Items, Desc) when is_function(Got) ->
+ is(lists:any(Got, Items), true, Desc);
+any(Got, Items, Desc) ->
+ is(lists:member(Got, Items), true, Desc).
+
+%% @spec none(Got, Items, Desc) -> Result
+%% Got = any()
+%% Items = [any()]
+%% Desc = string()
+%% Result = true | false
+%% @doc Assert that an item is not in a list.
+none(Got, Items, Desc) when is_function(Got) ->
+ is(lists:any(Got, Items), false, Desc);
+none(Got, Items, Desc) ->
+ is(lists:member(Got, Items), false, Desc).
+
+%% @spec fun_is(Fun, Expected, Desc) -> Result
+%% Fun = function()
+%% Expected = any()
+%% Desc = string()
+%% Result = true | false
+%% @doc Use an anonymous function to assert a pattern match.
+fun_is(Fun, Expected, Desc) when is_function(Fun) ->
+ is(Fun(Expected), true, Desc).
+
+%% @spec expect_fun(ExpectFun, Got, Desc) -> Result
+%% ExpectFun = function()
+%% Got = any()
+%% Desc = string()
+%% Result = true | false
+%% @doc Use an anonymous function to assert a pattern match, using actual
+%% value as the argument to the function.
+expect_fun(ExpectFun, Got, Desc) ->
+ evaluate(ExpectFun(Got), Got, ExpectFun, Desc).
+
+%% @spec expect_fun(ExpectFun, Got, Desc, ExpectStr) -> Result
+%% ExpectFun = function()
+%% Got = any()
+%% Desc = string()
+%% ExpectStr = string()
+%% Result = true | false
+%% @doc Use an anonymous function to assert a pattern match, using actual
+%% value as the argument to the function.
+expect_fun(ExpectFun, Got, Desc, ExpectStr) ->
+ evaluate(ExpectFun(Got), Got, ExpectStr, Desc).
+
+%% @equiv skip(TestFun, "")
+skip(TestFun) when is_function(TestFun) ->
+ skip(TestFun, "").
+
+%% @spec skip(TestFun, Reason) -> ok
+%% TestFun = function()
+%% Reason = string()
+%% @doc Skip a test.
+skip(TestFun, Reason) when is_function(TestFun), is_list(Reason) ->
+ begin_skip(Reason),
+ catch TestFun(),
+ end_skip(),
+ ok.
+
+%% @spec skip(Q, TestFun, Reason) -> ok
+%% Q = true | false | function()
+%% TestFun = function()
+%% Reason = string()
+%% @doc Skips a test conditionally. The first argument to this function can
+%% either be the 'true' or 'false' atoms or a function that returns 'true' or
+%% 'false'.
+skip(QFun, TestFun, Reason) when is_function(QFun), is_function(TestFun), is_list(Reason) ->
+ case QFun() of
+ true -> begin_skip(Reason), TestFun(), end_skip();
+ _ -> TestFun()
+ end,
+ ok;
+
+skip(Q, TestFun, Reason) when is_function(TestFun), is_list(Reason), Q == true ->
+ begin_skip(Reason),
+ TestFun(),
+ end_skip(),
+ ok;
+
+skip(_, TestFun, Reason) when is_function(TestFun), is_list(Reason) ->
+ TestFun(),
+ ok.
+
+%% @private
+begin_skip(Reason) ->
+ etap_server ! {self(), begin_skip, Reason}.
+
+%% @private
+end_skip() ->
+ etap_server ! {self(), end_skip}.
+
+%% @spec contains_ok(string(), string(), string()) -> true | false
+%% @doc Assert that a string is contained in another string.
+contains_ok(Source, String, Desc) ->
+ etap:isnt(
+ string:str(Source, String),
+ 0,
+ Desc
+ ).
+
+%% @spec is_before(string(), string(), string(), string()) -> true | false
+%% @doc Assert that a string comes before another string within a larger body.
+is_before(Source, StringA, StringB, Desc) ->
+ etap:is_greater(
+ string:str(Source, StringB),
+ string:str(Source, StringA),
+ Desc
+ ).
+
+%% @doc Assert that a given variable is a pid.
+is_pid(Pid, Desc) when is_pid(Pid) -> etap:ok(true, Desc);
+is_pid(_, Desc) -> etap:ok(false, Desc).
+
+%% @doc Assert that a given process/pid is alive.
+is_alive(Pid, Desc) ->
+ etap:ok(erlang:is_process_alive(Pid), Desc).
+
+%% @doc Assert that the current function of a pid is a given {M, F, A} tuple.
+is_mfa(Pid, MFA, Desc) ->
+ etap:is({current_function, MFA}, erlang:process_info(Pid, current_function), Desc).
+
+%% @spec loaded_ok(atom(), string()) -> true | false
+%% @doc Assert that a module has been loaded successfully.
+loaded_ok(M, Desc) when is_atom(M) ->
+ etap:fun_is(fun({module, _}) -> true; (_) -> false end, code:load_file(M), Desc).
+
+%% @spec can_ok(atom(), atom()) -> true | false
+%% @doc Assert that a module exports a given function.
+can_ok(M, F) when is_atom(M), is_atom(F) ->
+ Matches = [X || {X, _} <- M:module_info(exports), X == F],
+ etap:ok(Matches > 0, lists:concat([M, " can ", F])).
+
+%% @spec can_ok(atom(), atom(), integer()) -> true | false
+%% @doc Assert that a module exports a given function with a given arity.
+can_ok(M, F, A) when is_atom(M); is_atom(F), is_number(A) ->
+ Matches = [X || X <- M:module_info(exports), X == {F, A}],
+ etap:ok(Matches > 0, lists:concat([M, " can ", F, "/", A])).
+
+%% @spec has_attrib(M, A) -> true | false
+%% M = atom()
+%% A = atom()
+%% @doc Asserts that a module has a given attribute.
+has_attrib(M, A) when is_atom(M), is_atom(A) ->
+ etap:isnt(
+ proplists:get_value(A, M:module_info(attributes), 'asdlkjasdlkads'),
+ 'asdlkjasdlkads',
+ lists:concat([M, " has attribute ", A])
+ ).
+
+%% @spec has_attrib(M, A. V) -> true | false
+%% M = atom()
+%% A = atom()
+%% V = any()
+%% @doc Asserts that a module has a given attribute with a given value.
+is_attrib(M, A, V) when is_atom(M) andalso is_atom(A) ->
+ etap:is(
+ proplists:get_value(A, M:module_info(attributes)),
+ [V],
+ lists:concat([M, "'s ", A, " is ", V])
+ ).
+
+%% @spec is_behavior(M, B) -> true | false
+%% M = atom()
+%% B = atom()
+%% @doc Asserts that a given module has a specific behavior.
+is_behaviour(M, B) when is_atom(M) andalso is_atom(B) ->
+ is_attrib(M, behaviour, B).
+
+%% @doc Assert that an exception is raised when running a given function.
+dies_ok(F, Desc) ->
+ case (catch F()) of
+ {'EXIT', _} -> etap:ok(true, Desc);
+ _ -> etap:ok(false, Desc)
+ end.
+
+%% @doc Assert that an exception is not raised when running a given function.
+lives_ok(F, Desc) ->
+ etap:is(try_this(F), success, Desc).
+
+%% @doc Assert that the exception thrown by a function matches the given exception.
+throws_ok(F, Exception, Desc) ->
+ try F() of
+ _ -> etap:ok(nok, Desc)
+ catch
+ _:E ->
+ etap:is(E, Exception, Desc)
+ end.
+
+%% @private
+%% @doc Run a function and catch any exceptions.
+try_this(F) when is_function(F, 0) ->
+ try F() of
+ _ -> success
+ catch
+ throw:E -> {throw, E};
+ error:E -> {error, E};
+ exit:E -> {exit, E}
+ end.
+
+%% @private
+%% @doc Start the etap_server process if it is not running already.
+ensure_test_server() ->
+ case whereis(etap_server) of
+ undefined ->
+ proc_lib:start(?MODULE, start_etap_server,[]);
+ _ ->
+ diag("The test server is already running.")
+ end.
+
+%% @private
+%% @doc Start the etap_server loop and register itself as the etap_server
+%% process.
+start_etap_server() ->
+ catch register(etap_server, self()),
+ proc_lib:init_ack(ok),
+ etap:test_server(#test_state{
+ planned = 0,
+ count = 0,
+ pass = 0,
+ fail = 0,
+ skip = 0,
+ skip_reason = ""
+ }).
+
+
+%% @private
+%% @doc The main etap_server receive/run loop. The etap_server receive loop
+%% responds to seven messages apperatining to failure or passing of tests.
+%% It is also used to initiate the testing process with the {_, plan, _}
+%% message that clears the current test state.
+test_server(State) ->
+ NewState = receive
+ {_From, plan, unknown} ->
+ io:format("# Current time local ~s~n", [datetime(erlang:localtime())]),
+ io:format("# Using etap version ~p~n", [ proplists:get_value(vsn, proplists:get_value(attributes, etap:module_info())) ]),
+ State#test_state{
+ planned = -1,
+ count = 0,
+ pass = 0,
+ fail = 0,
+ skip = 0,
+ skip_reason = ""
+ };
+ {_From, plan, N} ->
+ io:format("# Current time local ~s~n", [datetime(erlang:localtime())]),
+ io:format("# Using etap version ~p~n", [ proplists:get_value(vsn, proplists:get_value(attributes, etap:module_info())) ]),
+ io:format("1..~p~n", [N]),
+ State#test_state{
+ planned = N,
+ count = 0,
+ pass = 0,
+ fail = 0,
+ skip = 0,
+ skip_reason = ""
+ };
+ {_From, begin_skip, Reason} ->
+ State#test_state{
+ skip = 1,
+ skip_reason = Reason
+ };
+ {_From, end_skip} ->
+ State#test_state{
+ skip = 0,
+ skip_reason = ""
+ };
+ {_From, pass, Desc} ->
+ FullMessage = skip_diag(
+ " - " ++ Desc,
+ State#test_state.skip,
+ State#test_state.skip_reason
+ ),
+ io:format("ok ~p ~s~n", [State#test_state.count + 1, FullMessage]),
+ State#test_state{
+ count = State#test_state.count + 1,
+ pass = State#test_state.pass + 1
+ };
+
+ {_From, fail, Desc} ->
+ FullMessage = skip_diag(
+ " - " ++ Desc,
+ State#test_state.skip,
+ State#test_state.skip_reason
+ ),
+ io:format("not ok ~p ~s~n", [State#test_state.count + 1, FullMessage]),
+ State#test_state{
+ count = State#test_state.count + 1,
+ fail = State#test_state.fail + 1
+ };
+ {From, state} ->
+ From ! State,
+ State;
+ {_From, diag, Message} ->
+ io:format("~s~n", [Message]),
+ State;
+ {From, count} ->
+ From ! State#test_state.count,
+ State;
+ {From, is_skip} ->
+ From ! State#test_state.skip,
+ State;
+ done ->
+ exit(normal)
+ end,
+ test_server(NewState).
+
+%% @private
+%% @doc Process the result of a test and send it to the etap_server process.
+mk_tap(Result, Desc) ->
+ IsSkip = lib:sendw(etap_server, is_skip),
+ case [IsSkip, Result] of
+ [_, true] ->
+ etap_server ! {self(), pass, Desc},
+ true;
+ [1, _] ->
+ etap_server ! {self(), pass, Desc},
+ true;
+ _ ->
+ etap_server ! {self(), fail, Desc},
+ false
+ end.
+
+%% @private
+%% @doc Format a date/time string.
+datetime(DateTime) ->
+ {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime,
+ 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]).
+
+%% @private
+%% @doc Craft an output message taking skip/todo into consideration.
+skip_diag(Message, 0, _) ->
+ Message;
+skip_diag(_Message, 1, "") ->
+ " # SKIP";
+skip_diag(_Message, 1, Reason) ->
+ " # SKIP : " ++ Reason.
44 test/util.erl
@@ -0,0 +1,44 @@
+-module(util).
+-export([test_good/1, test_good/2, test_errors/1]).
+
+test_good(Cases) ->
+ test_good(Cases, []).
+
+test_good(Cases, Options) ->
+ lists:foreach(fun(Case) -> check_good(Case, Options) end, Cases).
+
+test_errors(Cases) ->
+ lists:foreach(fun(Case) -> check_error(Case) end, Cases).
+
+ok_dec(J, _E) ->
+ lists:flatten(io_lib:format("Decoded ~p.", [J])).
+
+ok_enc(E, _J) ->
+ lists:flatten(io_lib:format("Encoded ~p", [E])).
+
+do_encode(E, Options) ->
+ iolist_to_binary(jiffy:encode(E, Options)).
+
+error_mesg(J) ->
+ lists:flatten(io_lib:format("Decoding ~p returns an error.", [J])).
+
+check_good({J, E}, Options) ->
+ etap:is(jiffy:decode(J), E, ok_dec(J, E)),
+ etap:is(do_encode(E, Options), J, ok_enc(E, J));
+check_good({J, E, J2}, Options) ->
+ etap:is(jiffy:decode(J), E, ok_dec(J, E)),
+ etap:is(do_encode(E, Options), J2, ok_enc(E, J2)).
+
+check_error({J, E}) ->
+ etap:fun_is(
+ fun({error, E1}) when E1 == E -> true; (E1) -> E1 end,
+ (catch jiffy:decode(J)),
+ error_mesg(J)
+ );
+check_error(J) ->
+ etap:fun_is(
+ fun({error, _}) -> true; (Else) -> Else end,
+ (catch jiffy:decode(J)),
+ error_mesg(J)
+ ).
+

0 comments on commit 21fe120

Please sign in to comment.