|
|
- %% Copyright (c) 2012, Magnus Klaar <klaar@ninenines.eu>
- %%
- %% Permission to use, copy, modify, and/or distribute this software for any
- %% purpose with or without fee is hereby granted, provided that the above
- %% copyright notice and this permission notice appear in all copies.
- %%
- %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-
- %% @doc Event filter implementation.
- %%
- %% An event query is constructed using the built in operators exported from
- %% this module. The filtering operators are used to specify which events
- %% should be included in the output of the query. The default output action
- %% is to copy all events matching the input filters associated with a query
- %% to the output. This makes it possible to construct and compose multiple
- %% queries at runtime.
- %%
- %% === Examples of built in filters ===
- %% ```
- %% %% Select all events where 'a' exists and is greater than 0.
- %% glc:gt(a, 0).
- %% %% Select all events where 'a' exists and is equal to 0.
- %% glc:eq(a, 0).
- %% %% Select all events where 'a' exists and is less than 0.
- %% glc:lt(a, 0).
- %%
- %% %% Select no input events. Used as black hole query.
- %% glc:null(false).
- %% %% Select all input events. Used as passthrough query.
- %% glc:null(true).
- %% '''
- %%
- %% === Examples of combining filters ===
- %% ```
- %% %% Select all events where both 'a' and 'b' exists and are greater than 0.
- %% glc:all([glc:gt(a, 0), glc:gt(b, 0)]).
- %% %% Select all events where 'a' or 'b' exists and are greater than 0.
- %% glc:any([glc:get(a, 0), glc:gt(b, 0)]).
- %% '''
- %%
- %% === Handling output events ===
- %%
- %% Once a query has been composed it is possible to override the output action
- %% with an erlang function. The function will be applied to each output event
- %% from the query. The return value from the function will be ignored.
- %%
- %% ```
- %% %% Write all input events as info reports to the error logger.
- %% glc:with(glc:null(true), fun(E) ->
- %% error_logger:info_report(gre:pairs(E)) end).
- %% '''
- %%
- -module(glc).
-
- -export([
- compile/2,
- handle/2,
- delete/1
- ]).
-
- -export([
- lt/2,
- eq/2,
- gt/2
- ]).
-
- -export([
- all/1,
- any/1,
- null/1,
- with/2
- ]).
-
- -record(module, {
- 'query' :: term(),
- tables :: [{atom(), ets:tid()}],
- qtree :: term()
- }).
-
- -type syntaxTree() :: erl_syntax:syntaxTree().
-
- -record(state, {
- event = undefined :: syntaxTree(),
- fields = [] :: [{atom(), syntaxTree()}],
- fieldc = 0 :: non_neg_integer(),
- paramvars = [] :: [{term(), syntaxTree()}],
- paramstab = undefined :: ets:tid()
- }).
-
- -type nextFun() :: fun((#state{}) -> [syntaxTree()]).
- -type q() :: tuple().
-
- -spec lt(atom(), term()) -> q().
- lt(Key, Term) when is_atom(Key) -> {Key, '<', Term}.
-
- -spec eq(atom(), term()) -> q().
- eq(Key, Term) when is_atom(Key) -> {Key, '=', Term}.
-
- -spec gt(atom(), term()) -> q().
- gt(Key, Term) when is_atom(Key) -> {Key, '>', Term}.
-
-
- %% @doc Apply multiple conditions to the input.
- %% For an event to be considered valid output the condition of all filters
- %% specified in the input must hold for the input event.
- -spec all([q()]) -> q().
- all(Conds) when is_list(Conds) ->
- {all, Conds}.
-
- %% @doc Apply one of multiple conditions to the input.
- -spec any([q()]) -> q().
- any(Conds) when is_list(Conds) ->
- {any, Conds}.
-
- %% @doc Always return `true' or `false'.
- -spec null(boolean()) -> q().
- null(Result) when is_boolean(Result) ->
- {null, Result}.
-
- %% @doc Apply a function to each output.
- -spec with(q(), fun((gre:event()) -> term())) -> q().
- with(Query, Fun) when is_function(Fun, 1) ->
- {with, Query, Fun}.
-
-
- %% @doc Compile a query to a module.
- %%
- %% On success the module representing the query is returned. The module and
- %% data associated with the query must be released using the {@link delete/1}
- %% function. The name of the query module is expected to be unique.
- -spec compile(atom(), list()) -> {ok, atom()}.
- compile(Module, Query) ->
- {ok, ModuleData} = module_data(Query),
- {ok, forms, Forms} = abstract_module(Module, ModuleData),
- {ok, Module, Binary} = compile_forms(Forms, []),
- {ok, loaded, Module} = load_binary(Module, Binary),
- {ok, Module}.
-
- %% @doc Handle an event using a compiled query.
- %%
- %% The input event is expected to have been returned from {@link gre:make/2}.
- -spec handle(atom(), gre:event()) -> ok.
- handle(Module, Event) ->
- Module:handle(Event).
-
- %% @doc Release a compiled query.
- %%
- %% This releases all resources allocated by a compiled query. The query name
- %% is expected to be associated with an existing query module. Calling this
- %% function will result in a runtime error.
- -spec delete(atom()) -> ok.
- delete(_Module) ->
- ok.
-
-
- %% @private Map a query to a module data term.
- -spec module_data(term()) -> {ok, #module{}}.
- module_data(Query) ->
- %% terms in the query which are not valid arguments to the
- %% erl_syntax:abstract/1 functions are stored in ETS.
- %% the terms are only looked up once they are necessary to
- %% continue evaluation of the query.
- Params = ets:new(params, [set,protected]),
- %% query counters are stored in a shared ETS table. this should
- %% be an optional feature. enable by defaults to simplify tests.
- Counters = ets:new(counters, [set,public]),
- ets:insert(Counters, [{input,0}, {filter,0}, {output,0}]),
- %% the abstract_tables/1 function expects a list of name-tid pairs.
- %% tables are referred to by name in the generated code. the table/1
- %% function maps names to tids.
- Tables = [{params,Params}, {counters,Counters}],
- Tree = query_tree(Query),
- {ok, #module{'query'=Query, tables=Tables, qtree=Tree}}.
-
-
- %% @private Map a query to a simplified query tree term.
- %%
- %% The simplified query tree is used to combine multiple queries into one
- %% query module. The goal of this is to reduce the filtering and dispatch
- %% overhead when multiple concurrent queries are executed.
- %%
- %% A fixed selection condition may be used to specify a property that an event
- %% must have in order to be considered part of the input stream for a query.
- %%
- %% For the sake of simplicity it is only possible to define selection
- %% conditions using the fields present in the context and identifiers
- %% of an event. The fields in the context are bound to the reserved
- %% names:
- %%
- %% - '$n': node name
- %% - '$a': application name
- %% - '$p': process identifier
- %% - '$t': timestamp
- %%
- %%
- %% If an event must be selected based on the runtime state of an event handler
- %% this must be done in the body of the handler.
- -type qcond() ::
- {atom(), '<', term()} |
- {atom(), '=', term()} |
- {atom(), '>', term()} |
- {any, [qcond()]} |
- {all, [qcond()]}.
- -type qbody() :: tuple().
- -type qtree() :: [{qcond(), qbody()}].
- -spec query_tree(term()) -> qtree().
- query_tree(Query) ->
- Query.
-
- %% abstract code geneation functions
-
- %% @private Generate an abstract dispatch module.
- -spec abstract_module(atom(), #module{}) -> {ok, forms, list()}.
- abstract_module(Module, Data) ->
- Forms = [erl_syntax:revert(E) || E <- abstract_module_(Module, Data)],
- case lists:keyfind(errors, 1, erl_syntax_lib:analyze_forms(Forms)) of
- false -> {ok, forms, Forms};
- {_, []} -> {ok, forms, Forms};
- {_, [_|_]}=Errors -> Errors
- end.
-
- %% @private Generate an abstract dispatch module.
- -spec abstract_module_(atom(), #module{}) -> [erl_syntax:syntaxTree()].
- abstract_module_(Module, #module{tables=Tables, qtree=Tree}=Data) ->
- {_, ParamsTable} = lists:keyfind(params, 1, Tables),
- AbstractMod = [
- %% -module(Module)
- erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
- %% -export([
- erl_syntax:attribute(
- erl_syntax:atom(export),
- [erl_syntax:list([
- %% info/1
- erl_syntax:arity_qualifier(
- erl_syntax:atom(info),
- erl_syntax:integer(1)),
- %% table/1
- erl_syntax:arity_qualifier(
- erl_syntax:atom(table),
- erl_syntax:integer(1)),
- %% handle/1
- erl_syntax:arity_qualifier(
- erl_syntax:atom(handle),
- erl_syntax:integer(1))])]),
- %% ]).
- %% info(Name) -> Term.
- erl_syntax:function(
- erl_syntax:atom(info),
- abstract_info(Data) ++
- [erl_syntax:clause(
- [erl_syntax:underscore()], none,
- [erl_syntax:application(
- erl_syntax:atom(erlang),
- erl_syntax:atom(error),
- [erl_syntax:atom(badarg)])])]),
- %% table(Name) -> ets:tid().
- erl_syntax:function(
- erl_syntax:atom(table),
- abstract_tables(Tables) ++
- [erl_syntax:clause(
- [erl_syntax:underscore()], none,
- [erl_syntax:application(
- erl_syntax:atom(erlang),
- erl_syntax:atom(error),
- [erl_syntax:atom(badarg)])])]),
- %% handle(Event) - entry function
- erl_syntax:function(
- erl_syntax:atom(handle),
- [erl_syntax:clause([erl_syntax:variable("Event")], none,
- [abstract_count(input),
- erl_syntax:application(none,
- erl_syntax:atom(handle_), [erl_syntax:variable("Event")])])]),
- %% input_(Node, App, Pid, Tags, Values) - filter roots
- erl_syntax:function(
- erl_syntax:atom(handle_),
- [erl_syntax:clause([erl_syntax:variable("Event")], none,
- abstract_filter(Tree, #state{
- event=erl_syntax:variable("Event"),
- paramstab=ParamsTable}))])
- ],
- %% Transform Term -> Key to Key -> Term
- ParamsList = [{K, V} || {V, K} <- ets:tab2list(ParamsTable)],
- ets:delete_all_objects(ParamsTable),
- ets:insert(ParamsTable, ParamsList),
- AbstractMod.
-
- %% @private Return the clauses of the table/1 function.
- abstract_tables(Tables) ->
- [erl_syntax:clause(
- [erl_syntax:abstract(K)], none,
- [erl_syntax:abstract(V)])
- || {K, V} <- Tables].
-
- %% @private Return the clauses of the info/1 function.
- abstract_info(#module{'query'=Query}) ->
- [erl_syntax:clause([erl_syntax:abstract(K)], none, V)
- || {K, V} <- [
- {'query', abstract_query(Query)},
- {input, abstract_getcount(input)},
- {filter, abstract_getcount(filter)},
- {output, abstract_getcount(output)}
- ]].
-
- %% @private Return the original query as an expression.
- abstract_query({with, _, _}) ->
- [erl_syntax:abstract([])];
- abstract_query(Query) ->
- [erl_syntax:abstract(Query)].
-
-
- %% @private Return a list of expressions to apply a filter.
- %% @todo Allow mulitple functions to be specified using `with/2'.
- -spec abstract_filter(q(), #state{}) -> [syntaxTree()].
- abstract_filter({with, Cond, Fun}, State) ->
- abstract_filter_(Cond,
- _OnMatch=fun(State2) ->
- [abstract_count(output)] ++ abstract_with(Fun, State2) end,
- _OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State);
- abstract_filter(Cond, State) ->
- abstract_filter_(Cond,
- _OnMatch=fun(_State2) -> [abstract_count(output)] end,
- _OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State).
-
- %% @private Return a list of expressions to apply a filter.
- %% A filter expects two continuation functions which generates the expressions
- %% to apply when the filter matches or fails to match. The state passed to the
- %% functions will be contain all variable bindings to previously accessed
- %% fields and parameters.
- -spec abstract_filter_(qcond(), nextFun(), nextFun(), #state{}) ->
- syntaxTree().
- abstract_filter_({null, true}, OnMatch, _OnNomatch, State) ->
- OnMatch(State);
- abstract_filter_({null, false}, _OnMatch, OnNomatch, State) ->
- OnNomatch(State);
- abstract_filter_({Key, Op, Value}, OnMatch, OnNomatch, State)
- when Op =:= '>'; Op =:= '='; Op =:= '<' ->
- Op2 = case Op of '=' -> '=:='; Op -> Op end,
- abstract_opfilter(Key, Op2, Value, OnMatch, OnNomatch, State);
- abstract_filter_({'any', Conds}, OnMatch, OnNomatch, State) ->
- abstract_any(Conds, OnMatch, OnNomatch, State);
- abstract_filter_({'all', Conds}, OnMatch, OnNomatch, State) ->
- abstract_all(Conds, OnMatch, OnNomatch, State).
-
- %% @private Return a branch based on a built in operator.
- -spec abstract_opfilter(atom(), atom(), term(), nextFun(),
- nextFun(), #state{}) -> [syntaxTree()].
- abstract_opfilter(Key, Opname, Value, OnMatch, OnNomatch, State) ->
- abstract_getkey(Key,
- _OnMatch=fun(#state{fields=Fields}=State2) ->
- {_, Field} = lists:keyfind(Key, 1, Fields),
- [erl_syntax:case_expr(
- erl_syntax:application(
- erl_syntax:atom(erlang), erl_syntax:atom(Opname),
- [Field, erl_syntax:abstract(Value)]),
- [erl_syntax:clause([erl_syntax:atom(true)], none,
- OnMatch(State2)),
- erl_syntax:clause([erl_syntax:atom(false)], none,
- OnNomatch(State2))])] end,
- _OnNomatch=fun(State2) -> OnNomatch(State2) end, State).
-
-
- %% @private Generate an `all' filter.
- %% An `all' filter is evaluated by testing all conditions that must hold. If
- %% any of the conditions does not hold the evaluation is short circuted at that
- %% point. This means that the `OnNomatch' branch is executed once for each
- %% condition. The `OnMatch' branch is only executed once.
- -spec abstract_all([qcond()], nextFun(), nextFun(), #state{}) ->
- [syntaxTree()].
- abstract_all([H|T], OnMatch, OnNomatch, State) ->
- abstract_filter_(H,
- _OnMatch=fun(State2) -> abstract_all(T, OnMatch, OnNomatch, State2)
- end, OnNomatch, State);
- abstract_all([], OnMatch, _OnNomatch, State) ->
- OnMatch(State).
-
- %% @private
- -spec abstract_any([qcond()], nextFun(), nextFun(), #state{}) ->
- [syntaxTree()].
- abstract_any([H|T], OnMatch, OnNomatch, State) ->
- abstract_filter_(H, OnMatch,
- _OnNomatch=fun(State2) -> abstract_any(T, OnMatch, OnNomatch, State2)
- end, State);
- abstract_any([], _OnMatch, OnNomatch, State) ->
- OnNomatch(State).
-
- %% @private
- -spec abstract_with(fun((gre:event()) -> term()), #state{}) -> [syntaxTree()].
- abstract_with(Fun, State) when is_function(Fun, 1) ->
- abstract_getparam(Fun, fun(#state{event=Event, paramvars=Params}) ->
- {_, Fun2} = lists:keyfind(Fun, 1, Params),
- [erl_syntax:application(none, Fun2, [Event])]
- end, State).
-
- %% @private Bind the value of a field to a variable.
- %% If the value of a field has already been bound to a variable the previous
- %% binding is reused over re-accessing the value. The `OnMatch' function is
- %% expected to access the variable stored in the state record. The `OnNomatch'
- %% function must not attempt to access the variable.
- -spec abstract_getkey(atom(), nextFun(), nextFun(), #state{}) ->
- [syntaxTree()].
- abstract_getkey(Key, OnMatch, OnNomatch, #state{fields=Fields}=State) ->
- case lists:keyfind(Key, 1, Fields) of
- {Key, _Variable} -> OnMatch(State);
- false -> abstract_getkey_(Key, OnMatch, OnNomatch, State)
- end.
-
-
- -spec abstract_getkey_(atom(), nextFun(), nextFun(), #state{}) ->
- [syntaxTree()].
- abstract_getkey_(Key, OnMatch, OnNomatch, #state{
- event=Event, fields=Fields}=State) ->
- [erl_syntax:case_expr(
- erl_syntax:application(
- erl_syntax:atom(gre), erl_syntax:atom(find),
- [erl_syntax:atom(Key), Event]),
- [erl_syntax:clause([
- erl_syntax:tuple([
- erl_syntax:atom(true),
- field_variable(Key)])], none,
- OnMatch(State#state{
- fields=[{Key, field_variable(Key)}|Fields]})),
- erl_syntax:clause([
- erl_syntax:atom(false)], none,
- OnNomatch(State))
- ]
- )].
-
- %% @private Bind the value of a parameter to a variable.
- %% During code generation the parameter value is used as the identity of the
- %% parameter. At runtime a unique integer is used as the identity.
- -spec abstract_getparam(term(), nextFun(), #state{}) -> [syntaxTree()].
- abstract_getparam(Term, OnBound, #state{paramvars=Params}=State) ->
- case lists:keyfind(Term, 1, Params) of
- {_, _Variable} -> OnBound(State);
- %% parameter not bound to variable in this scope.
- false -> abstract_getparam_(Term, OnBound, State)
- end.
-
-
- -spec abstract_getparam_(term(), nextFun(), #state{}) -> [syntaxTree()].
- abstract_getparam_(Term, OnBound, #state{paramstab=Table,
- paramvars=Params}=State) ->
- Key = case ets:lookup(Table, Term) of
- [{_, Key2}] ->
- Key2;
- [] ->
- Key2 = ets:info(Table, size),
- ets:insert(Table, {Term, Key2}),
- Key2
- end,
- [erl_syntax:match_expr(
- param_variable(Key),
- erl_syntax:application(
- erl_syntax:atom(ets),
- erl_syntax:atom(lookup_element),
- [erl_syntax:application(
- erl_syntax:atom(table),
- [erl_syntax:atom(params)]),
- erl_syntax:abstract(Key),
- erl_syntax:abstract(2)]))
- ] ++ OnBound(State#state{paramvars=[{Term, param_variable(Key)}|Params]}).
-
- %% @private Generate a variable name for the value of a field.
- %% @todo Encode non-alphanumeric characters as integer values.
- -spec field_variable(atom()) -> syntaxTree().
- field_variable(Key) ->
- erl_syntax:variable("Field_" ++ atom_to_list(Key)).
-
- %% @private Generate a variable name for the value of a parameter.
- -spec param_variable(integer()) -> syntaxTree().
- param_variable(Key) ->
- erl_syntax:variable("Param_" ++ integer_to_list(Key)).
-
-
- %% @private Return an expression to increment a counter.
- %% @todo Pass state record. Only Generate code if `statistics' is enabled.
- -spec abstract_count(atom()) -> syntaxTree().
- abstract_count(Counter) ->
- erl_syntax:application(
- erl_syntax:atom(ets),
- erl_syntax:atom(update_counter),
- [erl_syntax:application(
- erl_syntax:atom(table),
- [erl_syntax:atom(counters)]),
- erl_syntax:abstract(Counter),
- erl_syntax:abstract({2,1})]).
-
-
- %% @private Return an expression to get the value of a counter.
- %% @todo Pass state record. Only Generate code if `statistics' is enabled.
- -spec abstract_getcount(atom()) -> [syntaxTree()].
- abstract_getcount(Counter) ->
- [erl_syntax:application(
- erl_syntax:atom(ets),
- erl_syntax:atom(lookup_element),
- [erl_syntax:application(
- erl_syntax:atom(table),
- [erl_syntax:atom(counters)]),
- erl_syntax:abstract(Counter),
- erl_syntax:abstract(2)])].
-
-
- %% abstract code util functions
-
-
- %% @private Compile an abstract module.
- -spec compile_forms(term(), [term()]) -> {ok, atom(), binary()}.
- compile_forms(Forms, _Opts) ->
- case compile:forms(Forms) of
- {ok, Module, Binary} ->
- {ok, Module, Binary};
- {ok, Module, Binary, _Warnings} ->
- {ok, Module, Binary};
- Error ->
- erlang:error({compile_forms, Error})
- end.
-
- %% @private Load a module binary.
- -spec load_binary(atom(), binary()) -> {ok, loaded, atom()}.
- load_binary(Module, Binary) ->
- case code:load_binary(Module, "", Binary) of
- {module, Module} -> {ok, loaded, Module};
- {error, Reason} -> exit({error_loading_module, Module, Reason})
- end.
-
-
- -ifdef(TEST).
- -include_lib("eunit/include/eunit.hrl").
-
- setup_query(Module, Query) ->
- ?assertNot(erlang:module_loaded(Module)),
- ?assertEqual({ok, Module}, case (catch compile(Module, Query)) of
- {'EXIT',_}=Error -> ?debugFmt("~p", [Error]), Error; Else -> Else end),
- ?assert(erlang:function_exported(Module, table, 1)),
- ?assert(erlang:function_exported(Module, handle, 1)),
- {compiled, Module}.
-
- nullquery_compiles_test() ->
- {compiled, Mod} = setup_query(testmod1, glc:null(false)),
- ?assertError(badarg, Mod:table(noexists)).
-
- params_table_exists_test() ->
- {compiled, Mod} = setup_query(testmod2, glc:null(false)),
- ?assert(is_integer(Mod:table(params))),
- ?assertMatch([_|_], ets:info(Mod:table(params))).
-
- nullquery_exists_test() ->
- {compiled, Mod} = setup_query(testmod3, glc:null(false)),
- ?assert(erlang:function_exported(Mod, info, 1)),
- ?assertError(badarg, Mod:info(invalid)),
- ?assertEqual({null, false}, Mod:info('query')).
-
- init_counters_test() ->
- {compiled, Mod} = setup_query(testmod4, glc:null(false)),
- ?assertEqual(0, Mod:info(input)),
- ?assertEqual(0, Mod:info(filter)),
- ?assertEqual(0, Mod:info(output)).
-
- filtered_event_test() ->
- %% If no selection condition is specified no inputs can match.
- {compiled, Mod} = setup_query(testmod5, glc:null(false)),
- glc:handle(Mod, gre:make([], [list])),
- ?assertEqual(1, Mod:info(input)),
- ?assertEqual(1, Mod:info(filter)),
- ?assertEqual(0, Mod:info(output)).
-
- nomatch_event_test() ->
- %% If a selection condition but no body is specified the event
- %% is expected to count as filtered out if the condition does
- %% not hold.
- {compiled, Mod} = setup_query(testmod6, glc:eq('$n', 'noexists@nohost')),
- glc:handle(Mod, gre:make([{'$n', 'noexists2@nohost'}], [list])),
- ?assertEqual(1, Mod:info(input)),
- ?assertEqual(1, Mod:info(filter)),
- ?assertEqual(0, Mod:info(output)).
-
- opfilter_eq_test() ->
- %% If a selection condition but no body is specified the event
- %% counts as input to the query, but not as filtered out.
- {compiled, Mod} = setup_query(testmod7, glc:eq('$n', 'noexists@nohost')),
- glc:handle(Mod, gre:make([{'$n', 'noexists@nohost'}], [list])),
- ?assertEqual(1, Mod:info(input)),
- ?assertEqual(0, Mod:info(filter)),
- ?assertEqual(1, Mod:info(output)),
- done.
-
-
- opfilter_gt_test() ->
- {compiled, Mod} = setup_query(testmod8, glc:gt(a, 1)),
- glc:handle(Mod, gre:make([{'a', 2}], [list])),
- ?assertEqual(1, Mod:info(input)),
- ?assertEqual(0, Mod:info(filter)),
- glc:handle(Mod, gre:make([{'a', 0}], [list])),
- ?assertEqual(2, Mod:info(input)),
- ?assertEqual(1, Mod:info(filter)),
- ?assertEqual(1, Mod:info(output)),
- done.
-
- opfilter_lt_test() ->
- {compiled, Mod} = setup_query(testmod9, glc:lt(a, 1)),
- glc:handle(Mod, gre:make([{'a', 0}], [list])),
- ?assertEqual(1, Mod:info(input)),
- ?assertEqual(0, Mod:info(filter)),
- ?assertEqual(1, Mod:info(output)),
- glc:handle(Mod, gre:make([{'a', 2}], [list])),
- ?assertEqual(2, Mod:info(input)),
- ?assertEqual(1, Mod:info(filter)),
- ?assertEqual(1, Mod:info(output)),
- done.
-
- allholds_op_test() ->
- {compiled, Mod} = setup_query(testmod10,
- glc:all([glc:eq(a, 1), glc:eq(b, 2)])),
- glc:handle(Mod, gre:make([{'a', 1}], [list])),
- glc:handle(Mod, gre:make([{'a', 2}], [list])),
- ?assertEqual(2, Mod:info(input)),
- ?assertEqual(2, Mod:info(filter)),
- glc:handle(Mod, gre:make([{'b', 1}], [list])),
- glc:handle(Mod, gre:make([{'b', 2}], [list])),
- ?assertEqual(4, Mod:info(input)),
- ?assertEqual(4, Mod:info(filter)),
- glc:handle(Mod, gre:make([{'a', 1},{'b', 2}], [list])),
- ?assertEqual(5, Mod:info(input)),
- ?assertEqual(4, Mod:info(filter)),
- ?assertEqual(1, Mod:info(output)),
- done.
-
- anyholds_op_test() ->
- {compiled, Mod} = setup_query(testmod11,
- glc:any([glc:eq(a, 1), glc:eq(b, 2)])),
- glc:handle(Mod, gre:make([{'a', 2}], [list])),
- glc:handle(Mod, gre:make([{'b', 1}], [list])),
- ?assertEqual(2, Mod:info(input)),
- ?assertEqual(2, Mod:info(filter)),
- glc:handle(Mod, gre:make([{'a', 1}], [list])),
- glc:handle(Mod, gre:make([{'b', 2}], [list])),
- ?assertEqual(4, Mod:info(input)),
- ?assertEqual(2, Mod:info(filter)),
- done.
-
- with_function_test() ->
- Self = self(),
- {compiled, Mod} = setup_query(testmod12,
- glc:with(glc:eq(a, 1), fun(Event) -> Self ! gre:fetch(a, Event) end)),
- glc:handle(Mod, gre:make([{a,1}], [list])),
- ?assertEqual(1, Mod:info(output)),
- ?assertEqual(1, receive Msg -> Msg after 0 -> notcalled end),
- done.
-
- -endif.
|