Permalink
Browse files

WIP add merge test, start update test

  • Loading branch information...
1 parent 1970825 commit acc751474dd1828552b28ee2c315b5e9a5f1f140 @russelldb russelldb committed Feb 11, 2013
Showing with 118 additions and 20 deletions.
  1. +8 −3 src/riak_kv_counter.erl
  2. +110 −17 test/kv_counter_eqc.erl
View
@@ -37,6 +37,8 @@
-spec update(riak_object:riak_object(), riak_object:index_specs(),
binary(), integer()) ->
riak_object:riak_object().
+update(RObj, _IndexSpecs, _Actor, 0) ->
+ RObj;
update(RObj, IndexSpecs, Actor, Amt) ->
{Counter0, NonCounterSiblings, Meta} = merge_object(RObj, IndexSpecs,
riak_kv_pncounter:new()),
@@ -89,9 +91,12 @@ merge_value(NonCounter, {Mergedest, NonCounterSiblings}) ->
%% therefore create a meta that is
%% only the index meta data we already know about
merged_meta(IndexSpecs) ->
- Indexes = [{Index, Value} || {Op, Index, Value} <- IndexSpecs,
- Op =:= add],
- dict:store(?MD_INDEX, Indexes, dict:new()).
+ case [{Index, Value} || {Op, Index, Value} <- IndexSpecs,
+ Op =:= add] of
+ [] -> dict:new();
+ Indexes ->
+ dict:store(?MD_INDEX, Indexes, dict:new())
+ end.
update_counter(Counter, Actor, Amt) ->
Op = counter_op(Amt),
View
@@ -54,12 +54,18 @@ test_merge() ->
test_merge(N) ->
quickcheck(numtests(N, prop_merge())).
+test_update() ->
+ test_update(100).
+
+test_update(N) ->
+ quickcheck(numtests(N, prop_update())).
+
prop_value() ->
%% given any riak_object,
%% value will return the value
%% of merged PN-Counter payloads (zero if no counters)
?FORALL(RObj, riak_object(),
- collect(num_counters(riak_object:get_values(RObj)),
+ collect(num_counters(RObj),
equals(sumthem(RObj), riak_kv_counter:value(RObj)))).
prop_merge() ->
@@ -73,36 +79,122 @@ prop_merge() ->
?FORALL({RObj, IndexSpecs}, {riak_object(), index_specs()},
begin
Merged = riak_kv_counter:merge(RObj, IndexSpecs),
- NumCounters = num_counters(riak_object:get_values(RObj)),
- NumMergedCounters = num_counters(riak_object:get_values(Merged)),
- ExpectedCounters = case NumCounters of
+ NumGeneratedCounters = num_counters(RObj),
+ NumMergedCounters = num_counters(Merged),
+ ExpectedCounters = case NumGeneratedCounters of
0 -> 0;
_ -> 1
end,
- %% Check the structure of the merged counter is correct
+ %% Check that the structure of the merged counter is correct
ExpectedCounter = merge_object(RObj, undefined),
- MergedCounter = single_counter(Merged),
+ {MergedMeta, MergedCounter} = single_counter(Merged),
%% Check that the meta is correct
+ ExpectedIndexMeta = expected_indexes(IndexSpecs, NumGeneratedCounters),
%% Check that non-sibling values and meta are untouched
ExpectedSiblings = non_counter_siblings(RObj),
ActualSiblings = non_counter_siblings(Merged),
?WHENFAIL(
begin
io:format("Gen ~p\n", [RObj]),
- io:format("Merged ~p\n", [Merged])
+ io:format("Merged ~p\n", [Merged]),
+ io:format("Index Specs ~p~n", [IndexSpecs])
end,
- collect(NumCounters,
- conjunction([{number_of_counters,
+ collect(NumGeneratedCounters,
+ conjunction([
+ {value, equals(sumthem(RObj), riak_kv_counter:value(Merged))},
+ {number_of_counters,
equals(ExpectedCounters, NumMergedCounters)},
{counter_structure,
counters_equal(ExpectedCounter, MergedCounter)},
- {siblings, equals(lists:sort(ExpectedSiblings), lists:sort(ActualSiblings))}
+ {siblings, equals(lists:sort(ExpectedSiblings), lists:sort(ActualSiblings))},
+ {index_meta, equals(ExpectedIndexMeta, sorted_index_meta(MergedMeta))}
])))
end).
+prop_update() ->
+ %% given any riak object, will update the counter value of that object.
+ %% If any counter values are present they will be merged and incremented
+ %% if not, then a new counter is created, and incremented
+ %% all non-counter values and meta are left alone
+ %% only counter index meta is preserved, again using indexspecs.
+ ?FORALL({RObj, IndexSpecs, Actor, Amt},
+ {riak_object(), index_specs(), noshrink(binary(4)), int()},
+ begin
+ Updated = riak_kv_counter:update(RObj, IndexSpecs, Actor, Amt),
+
+ ?WHENFAIL(
+ begin
+ io:format("Gen ~p~n", [RObj]),
+ io:format("Updated ~p~n", [Updated]),
+ io:format("Index Specs ~p~n", [IndexSpecs]),
+ io:format("Amt ~p~n", [Amt])
+ end,
+ collect(Amt,
+ conjunction([
+ {counter_value, equals(sumthem(RObj) + Amt,
+ riak_kv_counter:value(Updated))},
+ {merge_checks, verify_merge(RObj, Updated, IndexSpecs)}
+ ])
+ ))
+ end).
+
+%% Both update and merge
+%% share most of their properties
+%% so reuses them
+verify_merge(Generated, PostAction, IndexSpecs) ->
+ NumGeneratedCounters = num_counters(Generated),
+ NumPostActionCounters = num_counters(PostAction),
+ ExpectedCounters = case NumGeneratedCounters of
+ 0 -> 0;
+ _ -> 1
+ end,
+ %% Check that the structure of the merged counter is correct
+ ExpectedCounter = merge_object(Generated, undefined),
+ {PostActionMeta, PostActionCounter} = single_counter(PostAction),
+ %% Check that the meta is correct
+ ExpectedIndexMeta = expected_indexes(IndexSpecs, NumGeneratedCounters),
+ %% Check that non-sibling values and meta are untouched
+ ExpectedSiblings = non_counter_siblings(Generated),
+ ActualSiblings = non_counter_siblings(PostAction),
+ conjunction([{number_of_counters,
+ equals(ExpectedCounters, NumPostActionCounters)},
+ {counter_structure,
+ counters_equal(ExpectedCounter, PostActionCounter)},
+ {siblings, equals(lists:sort(ExpectedSiblings), lists:sort(ActualSiblings))},
+ {index_meta, equals(ExpectedIndexMeta, sorted_index_meta(PostActionMeta))}
+ ]).
+
%%====================================================================
%% Helpers
%%====================================================================
+sorted_index_meta(undefined) ->
+ undefined;
+sorted_index_meta(Meta) ->
+ case dict:find(?MD_INDEX, Meta) of
+ error ->
+ undefined;
+ {ok, Val} ->
+ lists:sort(Val)
+ end.
+
+expected_indexes(_IndexSpecs, 0) ->
+ undefined;
+expected_indexes(IndexSpecs, _) ->
+ %% Use fold just to differentiate the code
+ %% under test and the testing code
+ case lists:foldl(fun({add, Index, Val}, Acc) ->
+ [{Index, Val} | Acc];
+ (_, Acc) ->
+ Acc
+ end,
+ [],
+ IndexSpecs) of
+ [] ->
+ undefined;
+ Indexes -> lists:sort(Indexes)
+ end.
+
+%% safe wrap of riak_kv_pncounter:equal/2
counters_equal(undefined, undefined) ->
true;
counters_equal(_C1, undefined) ->
@@ -112,17 +204,18 @@ counters_equal(undefined, _C2) ->
counters_equal(C1, C2) ->
riak_kv_pncounter:equal(C1, C2).
+%% Extract a single {meta , counter} value
single_counter(Merged) ->
- Values = riak_object:get_values(Merged),
+ Contents = riak_object:get_contents(Merged),
case [begin
{riak_kv_pncounter, Counter} = Val,
- Counter
- end|| Val <- Values,
+ {Meta, Counter}
+ end|| {Meta, Val} <- Contents,
is_tuple(Val),
riak_kv_pncounter =:= element(1, Val)] of
[Single] ->
Single;
- _Many -> undefined
+ _Many -> {undefined, undefined}
end.
non_counter_siblings(RObj) ->
@@ -135,7 +228,8 @@ non_counter_siblings(RObj) ->
Contents),
NonCounters.
-num_counters(Values) ->
+num_counters(RObj) ->
+ Values = riak_object:get_values(RObj),
length([ok || Val <- Values,
is_tuple(Val),
riak_kv_pncounter =:= element(1, Val)]).
@@ -160,7 +254,6 @@ sumthem(RObj) ->
%%====================================================================
%% Generators
%%====================================================================
-
riak_object() ->
?LET({Contents, VClock},
{contents(), fsm_eqc_util:vclock()},
@@ -184,7 +277,7 @@ metadatas() ->
list(metadatum()).
metadatum() ->
- %% doesn't need to be realistic, even
+ %% doesn't need to be realistic,
%% just present
{binary(), binary()}.

0 comments on commit acc7514

Please sign in to comment.