Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limiting the size/complexity of the commands generated by proper_fsm #315

Open
rvangraan opened this issue Dec 8, 2023 · 1 comment
Open

Comments

@rvangraan
Copy link

rvangraan commented Dec 8, 2023

Hi there,

I'm working on tests that rely on proper_fsm. I'm noticing that proper_fsm:commands(?MODULE) generates truly gigantic command sequences. I'm using a lot of inferred state, i.e. delayed symbolic evaluations to effect state and it seems that sometimes these things becoming recursive. With that I mean code like this, that uses symbolic functions to calculate state:

...
next_state_data(try_connect_client, _, S, Result, {call,_,connect,_}) ->
    S#{
        client_state => {call, ?MODULE, derive_client_state, [Result]}
    };
next_state_data(client_connected, _, S, Result, {call,_,tcp_send,[_, Payload]}) ->
    S#{
        payloads_sent => {call, ?MODULE, derive_add_payload_if_sent, [S, Result, Payload]},
        payload_counter => {call, ?MODULE, derive_payload_counter, [S, Result]}     
    };
next_state_data(client_connected, _, S, _Result, {call,_,disconnect,_}) ->
    S#{client_state => disconnected}.

Some of the more extreme examples (That don't blow up):

Cmd len: 26 size: 92,736,416 bytes

If I sample a few of the outputs, you'll see the delayed evaluation calls:

{ok,{forall,[{set,{var,1},
                  {call,gen_tcp_test_proxy,resume,[{var,'PID'}]}},
             {set,{var,2},
                  {call,gen_tcp_test_proxy,connect,[{var,'PID'}]}},
             {set,{var,3},
                  {call,gen_tcp_test_proxy,tcp_send,
                        [{var,'PID'},
                         {call,gen_tcp_server_layer_tests,payload,
                               [#{payload_counter => 1,lower_layer_state => offline,
                                  client_state =>
                                      {call,gen_tcp_server_layer_tests,derive_client_state,
                                            [{var,2}]},
                                  stack_state => offline}]}]}},
             {set,{var,4},
                  {call,gen_tcp_test_proxy,disconnect,[{var,'PID'}]}},
             {set,{var,5},
                  {call,gen_tcp_test_proxy,connect,[{var,'PID'}]}},
             {set,{var,6},
                  {call,gen_tcp_test_proxy,disconnect,[{var,'PID'}]}},
             {set,{var,7},
                  {call,gen_tcp_test_proxy,signal_from_bottom,
                        [{var,'PID'},layer_up]}},
             {set,{var,8},
                  {call,gen_tcp_test_proxy,connect,[{var,'PID'}]}}],

This code works fine for shorter sequences, but the generator produces gigantic examples, that literally causes my beam VM to run out of memory.

So is there a way to limit the size of these things?

Here is a (bigger) example. There is nothing wrong with this test case, it does what it's supposed to. The issue is that the generator produces much, much bigger ones.

{ok,{forall,[{set,{var,1},
                  {call,gen_tcp_test_proxy,resume,[{var,'PID'}]}},
             {set,{var,2},
                  {call,gen_tcp_test_proxy,connect,[{var,'PID'}]}},
             {set,{var,3},
                  {call,gen_tcp_test_proxy,tcp_send,
                        [{var,'PID'},
                         {call,gen_tcp_server_layer_tests,payload,
                               [#{payload_counter => 1,lower_layer_state => offline,
                                  client_state =>
                                      {call,gen_tcp_server_layer_tests,derive_client_state,
                                            [{var,2}]},
                                  stack_state => offline}]}]}},
             {set,{var,4},
                  {call,gen_tcp_test_proxy,tcp_send,
                        [{var,'PID'},
                         {call,gen_tcp_server_layer_tests,payload,
                               [#{payloads_sent =>
                                      {call,gen_tcp_server_layer_tests,derive_add_payload_if_sent,
                                            [#{payload_counter => 1,lower_layer_state => offline,
                                               client_state =>
                                                   {call,gen_tcp_server_layer_tests,derive_client_state,
                                                         [{var,2}]},
                                               stack_state => offline},
                                             {var,3},
                                             {call,gen_tcp_server_layer_tests,payload,
                                                   [#{payload_counter => 1,lower_layer_state => offline,
                                                      client_state =>
                                                          {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                [{var,2}]},
                                                      stack_state => offline}]}]},
                                  payload_counter =>
                                      {call,gen_tcp_server_layer_tests,derive_payload_counter,
                                            [#{payload_counter => 1,lower_layer_state => offline,
                                               client_state =>
                                                   {call,gen_tcp_server_layer_tests,derive_client_state,
                                                         [{var,2}]},
                                               stack_state => offline},
                                             {var,3}]},
                                  lower_layer_state => offline,
                                  client_state =>
                                      {call,gen_tcp_server_layer_tests,derive_client_state,
                                            [{var,2}]},
                                  stack_state => offline}]}]}},
             {set,{var,5},
                  {call,gen_tcp_test_proxy,tcp_send,
                        [{var,'PID'},
                         {call,gen_tcp_server_layer_tests,payload,
                               [#{payloads_sent =>
                                      {call,gen_tcp_server_layer_tests,derive_add_payload_if_sent,
                                            [#{payloads_sent =>
                                                   {call,gen_tcp_server_layer_tests,derive_add_payload_if_sent,
                                                         [#{payload_counter => 1,lower_layer_state => offline,
                                                            client_state =>
                                                                {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                      [{var,2}]},
                                                            stack_state => offline},
                                                          {var,3},
                                                          {call,gen_tcp_server_layer_tests,payload,
                                                                [#{payload_counter => 1,lower_layer_state => offline,
                                                                   client_state =>
                                                                       {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                             [{var,2}]},
                                                                   stack_state => offline}]}]},
                                               payload_counter =>
                                                   {call,gen_tcp_server_layer_tests,derive_payload_counter,
                                                         [#{payload_counter => 1,lower_layer_state => offline,
                                                            client_state =>
                                                                {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                      [{var,2}]},
                                                            stack_state => offline},
                                                          {var,3}]},
                                               lower_layer_state => offline,
                                               client_state =>
                                                   {call,gen_tcp_server_layer_tests,derive_client_state,
                                                         [{var,2}]},
                                               stack_state => offline},
                                             {var,4},
                                             {call,gen_tcp_server_layer_tests,payload,
                                                   [#{payloads_sent =>
                                                          {call,gen_tcp_server_layer_tests,derive_add_payload_if_sent,
                                                                [#{payload_counter => 1,lower_layer_state => offline,
                                                                   client_state =>
                                                                       {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                             [{var,2}]},
                                                                   stack_state => offline},
                                                                 {var,3},
                                                                 {call,gen_tcp_server_layer_tests,payload,
                                                                       [#{payload_counter => 1,lower_layer_state => offline,
                                                                          client_state =>
                                                                              {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                                    [{var,2}]},
                                                                          stack_state => offline}]}]},
                                                      payload_counter =>
                                                          {call,gen_tcp_server_layer_tests,derive_payload_counter,
                                                                [#{payload_counter => 1,lower_layer_state => offline,
                                                                   client_state =>
                                                                       {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                             [{var,2}]},
                                                                   stack_state => offline},
                                                                 {var,3}]},
                                                      lower_layer_state => offline,
                                                      client_state =>
                                                          {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                [{var,2}]},
                                                      stack_state => offline}]}]},
                                  payload_counter =>
                                      {call,gen_tcp_server_layer_tests,derive_payload_counter,
                                            [#{payloads_sent =>
                                                   {call,gen_tcp_server_layer_tests,derive_add_payload_if_sent,
                                                         [#{payload_counter => 1,lower_layer_state => offline,
                                                            client_state =>
                                                                {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                      [{var,2}]},
                                                            stack_state => offline},
                                                          {var,3},
                                                          {call,gen_tcp_server_layer_tests,payload,
                                                                [#{payload_counter => 1,lower_layer_state => offline,
                                                                   client_state =>
                                                                       {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                             [{var,2}]},
                                                                   stack_state => offline}]}]},
                                               payload_counter =>
                                                   {call,gen_tcp_server_layer_tests,derive_payload_counter,
                                                         [#{payload_counter => 1,lower_layer_state => offline,
                                                            client_state =>
                                                                {call,gen_tcp_server_layer_tests,derive_client_state,
                                                                      [{var,2}]},
                                                            stack_state => offline},
                                                          {var,3}]},
                                               lower_layer_state => offline,
                                               client_state =>
                                                   {call,gen_tcp_server_layer_tests,derive_client_state,
                                                         [{var,2}]},
                                               stack_state => offline},
                                             {var,4}]},
                                  lower_layer_state => offline,
                                  client_state =>
                                      {call,gen_tcp_server_layer_tests,derive_client_state,
                                            [{var,2}]},
                                  stack_state => offline}]}]}},
             {set,{var,6},
                  {call,gen_tcp_test_proxy,disconnect,[{var,'PID'}]}},
             {set,{var,7},
                  {call,gen_tcp_test_proxy,signal_from_bottom,
                        [{var,'PID'},layer_up]}}],
            #Fun<gen_tcp_server_layer_tests.2.77195010>}}

Thanks!

R

@rvangraan
Copy link
Author

rvangraan commented Dec 11, 2023

Update: I've implemented a work-around that successfully avoids the massive test cases:

precondition(_, _,S = #{}, {call, _, Call, _}) when Call =:= tcp_send orelse Call =:= connect ->
    Size = size(term_to_binary(S)),
    case Size > ?MEGABYTE(?MAX_DEEP_STATE_SIZE_MB) of
        true ->
            io:format("\nState size: ~p exceeds maximum threshold of ~pMb attempting call ~300p. Skipping", [Size, ?MAX_DEEP_STATE_SIZE_MB,Call]);
        false ->
            ok
    end,
    Size < ?MEGABYTE(?MAX_DEEP_STATE_SIZE_MB);
precondition(_, _,_S = #{}, _call) ->
    true.

Basically, the calls that result in deep state mutations have been limited by a precondition that checks the size of the symbolic state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant