@@ -28,7 +28,7 @@ import {
28
28
import { Library , Testcase , TestcaseType , TestcaseTypes } from '../../assessment/AssessmentTypes' ;
29
29
import { mockRuntimeContext } from '../../mocks/ContextMocks' ;
30
30
import { mockTestcases } from '../../mocks/GradingMocks' ;
31
- import { reportInfiniteLoopError } from '../../utils/InfiniteLoopReporter' ;
31
+ import { InfiniteLoopErrorType , reportInfiniteLoopError } from '../../utils/InfiniteLoopReporter' ;
32
32
import { showSuccessMessage , showWarningMessage } from '../../utils/NotificationsHelper' ;
33
33
import {
34
34
beginClearContext ,
@@ -64,6 +64,11 @@ function generateDefaultState(
64
64
) : OverallState {
65
65
return {
66
66
...defaultState ,
67
+ session : {
68
+ ...defaultState . session ,
69
+ experimentApproval : false ,
70
+ experimentCoinflip : true
71
+ } ,
67
72
workspaces : {
68
73
...defaultState . workspaces ,
69
74
[ workspaceLocation ] : {
@@ -130,7 +135,8 @@ describe('EVAL_EDITOR', () => {
130
135
scheduler : 'preemptive' ,
131
136
originalMaxExecTime : execTime ,
132
137
stepLimit : 1000 ,
133
- useSubst : false
138
+ useSubst : false ,
139
+ throwInfiniteLoops : true
134
140
}
135
141
]
136
142
} )
@@ -148,7 +154,8 @@ describe('EVAL_EDITOR', () => {
148
154
scheduler : 'preemptive' ,
149
155
originalMaxExecTime : execTime ,
150
156
stepLimit : 1000 ,
151
- useSubst : false
157
+ useSubst : false ,
158
+ throwInfiniteLoops : true
152
159
}
153
160
]
154
161
} )
@@ -212,7 +219,8 @@ describe('EVAL_REPL', () => {
212
219
scheduler : 'preemptive' ,
213
220
originalMaxExecTime : 1000 ,
214
221
stepLimit : 1000 ,
215
- useSubst : false
222
+ useSubst : false ,
223
+ throwInfiniteLoops : true
216
224
} )
217
225
. dispatch ( {
218
226
type : EVAL_REPL ,
@@ -641,7 +649,8 @@ describe('evalCode', () => {
641
649
scheduler : 'preemptive' ,
642
650
originalMaxExecTime : 1000 ,
643
651
stepLimit : 1000 ,
644
- useSubst : false
652
+ useSubst : false ,
653
+ throwInfiniteLoops : true
645
654
} ;
646
655
lastDebuggerResult = { status : 'error' } ;
647
656
state = generateDefaultState ( workspaceLocation ) ;
@@ -656,7 +665,8 @@ describe('evalCode', () => {
656
665
scheduler : 'preemptive' ,
657
666
originalMaxExecTime : execTime ,
658
667
stepLimit : 1000 ,
659
- useSubst : false
668
+ useSubst : false ,
669
+ throwInfiniteLoops : true
660
670
} )
661
671
. put ( evalInterpreterSuccess ( value , workspaceLocation ) )
662
672
. silentRun ( ) ;
@@ -670,7 +680,8 @@ describe('evalCode', () => {
670
680
scheduler : 'preemptive' ,
671
681
originalMaxExecTime : execTime ,
672
682
stepLimit : 1000 ,
673
- useSubst : false
683
+ useSubst : false ,
684
+ throwInfiniteLoops : true
674
685
} )
675
686
. put ( endDebuggerPause ( workspaceLocation ) )
676
687
. put ( evalInterpreterSuccess ( 'Breakpoint hit!' , workspaceLocation ) )
@@ -684,7 +695,8 @@ describe('evalCode', () => {
684
695
scheduler : 'preemptive' ,
685
696
originalMaxExecTime : execTime ,
686
697
stepLimit : 1000 ,
687
- useSubst : false
698
+ useSubst : false ,
699
+ throwInfiniteLoops : true
688
700
} )
689
701
. put . like ( { action : { type : EVAL_INTERPRETER_ERROR } } )
690
702
. silentRun ( ) ;
@@ -707,17 +719,22 @@ describe('evalCode', () => {
707
719
scheduler : 'preemptive' ,
708
720
originalMaxExecTime : execTime ,
709
721
stepLimit : 1000 ,
710
- useSubst : false
722
+ useSubst : false ,
723
+ throwInfiniteLoops : true
711
724
} )
712
725
. put ( evalInterpreterError ( context . errors , workspaceLocation ) )
713
726
. silentRun ( ) ;
714
727
} ) ;
715
728
716
729
test ( 'calls reportInfiniteLoop on error and sends correct data to sentry' , ( ) => {
717
- context = createContext ( 3 ) ;
718
- const code1 = 'const test=[(x)=>x,2,3,[(x)=>x],5];function f(x){return f(x);}' ;
730
+ state = {
731
+ ...state ,
732
+ session : { ...state . session , experimentApproval : true , experimentCoinflip : true }
733
+ } ;
734
+ const thisContext = createContext ( 3 ) ;
735
+ context = thisContext ;
736
+ const code1 = 'function f(x){f(x);}' ;
719
737
const code2 = 'f(1);' ;
720
- state = generateDefaultState ( workspaceLocation , { } ) ;
721
738
722
739
return runInContext ( code1 , context , {
723
740
scheduler : 'preemptive' ,
@@ -728,12 +745,64 @@ describe('evalCode', () => {
728
745
. withState ( state )
729
746
. call (
730
747
reportInfiniteLoopError ,
731
- 'source_protection_recursion' ,
732
- 'function is_list(xs) {\n return is_null(xs) || is_pair(xs) && is_list(tail(xs));\n}\nfunction equal(xs, ys) {\n return is_pair(xs) ? is_pair(ys) && equal(head(xs), head(ys)) && equal(tail(xs), tail(ys)) : is_null(xs) ? is_null(ys) : is_number(xs) ? is_number(ys) && xs === ys : is_boolean(xs) ? is_boolean(ys) && (xs && ys || !xs && !ys) : is_string(xs) ? is_string(ys) && xs === ys : is_undefined(xs) ? is_undefined(ys) : is_function(ys) && xs === ys;\n}\nfunction $length(xs, acc) {\n return is_null(xs) ? acc : $length(tail(xs), acc + 1);\n}\nfunction length(xs) {\n return $length(xs, 0);\n}\nfunction $map(f, xs, acc) {\n return is_null(xs) ? reverse(acc) : $map(f, tail(xs), pair(f(head(xs)), acc));\n}\nfunction map(f, xs) {\n return $map(f, xs, null);\n}\nfunction $build_list(i, fun, already_built) {\n return i < 0 ? already_built : $build_list(i - 1, fun, pair(fun(i), already_built));\n}\nfunction build_list(fun, n) {\n return $build_list(n - 1, fun, null);\n}\nfunction for_each(fun, xs) {\n if (is_null(xs)) {\n return true;\n } else {\n fun(head(xs));\n return for_each(fun, tail(xs));\n }\n}\nfunction $list_to_string(xs, cont) {\n return is_null(xs) ? cont("null") : is_pair(xs) ? $list_to_string(head(xs), x => $list_to_string(tail(xs), y => cont("[" + x + "," + y + "]"))) : cont(stringify(xs));\n}\nfunction list_to_string(xs) {\n return $list_to_string(xs, x => x);\n}\nfunction $reverse(original, reversed) {\n return is_null(original) ? reversed : $reverse(tail(original), pair(head(original), reversed));\n}\nfunction reverse(xs) {\n return $reverse(xs, null);\n}\nfunction $append(xs, ys, cont) {\n return is_null(xs) ? cont(ys) : $append(tail(xs), ys, zs => cont(pair(head(xs), zs)));\n}\nfunction append(xs, ys) {\n return $append(xs, ys, xs => xs);\n}\nfunction member(v, xs) {\n return is_null(xs) ? null : v === head(xs) ? xs : member(v, tail(xs));\n}\nfunction $remove(v, xs, acc) {\n return is_null(xs) ? append(reverse(acc), xs) : v === head(xs) ? append(reverse(acc), tail(xs)) : $remove(v, tail(xs), pair(head(xs), acc));\n}\nfunction remove(v, xs) {\n return $remove(v, xs, null);\n}\nfunction $remove_all(v, xs, acc) {\n return is_null(xs) ? append(reverse(acc), xs) : v === head(xs) ? $remove_all(v, tail(xs), acc) : $remove_all(v, tail(xs), pair(head(xs), acc));\n}\nfunction remove_all(v, xs) {\n return $remove_all(v, xs, null);\n}\nfunction $filter(pred, xs, acc) {\n return is_null(xs) ? reverse(acc) : pred(head(xs)) ? $filter(pred, tail(xs), pair(head(xs), acc)) : $filter(pred, tail(xs), acc);\n}\nfunction filter(pred, xs) {\n return $filter(pred, xs, null);\n}\nfunction $enum_list(start, end, acc) {\n return start > end ? reverse(acc) : $enum_list(start + 1, end, pair(start, acc));\n}\nfunction enum_list(start, end) {\n return $enum_list(start, end, null);\n}\nfunction list_ref(xs, n) {\n return n === 0 ? head(xs) : list_ref(tail(xs), n - 1);\n}\nfunction $accumulate(f, initial, xs, cont) {\n return is_null(xs) ? cont(initial) : $accumulate(f, initial, tail(xs), x => cont(f(head(xs), x)));\n}\nfunction accumulate(f, initial, xs) {\n return $accumulate(f, initial, xs, x => x);\n}\nfunction is_stream(xs) {\n return is_null(xs) || is_pair(xs) && is_stream(stream_tail(xs));\n}\nfunction list_to_stream(xs) {\n return is_null(xs) ? null : pair(head(xs), () => list_to_stream(tail(xs)));\n}\nfunction stream_to_list(xs) {\n return is_null(xs) ? null : pair(head(xs), stream_to_list(stream_tail(xs)));\n}\nfunction stream_length(xs) {\n return is_null(xs) ? 0 : 1 + stream_length(stream_tail(xs));\n}\nfunction stream_map(f, s) {\n return is_null(s) ? null : pair(f(head(s)), () => stream_map(f, stream_tail(s)));\n}\nfunction build_stream(fun, n) {\n function build(i) {\n return i >= n ? null : pair(fun(i), () => build(i + 1));\n }\n return build(0);\n}\nfunction stream_for_each(fun, xs) {\n if (is_null(xs)) {\n return true;\n } else {\n fun(head(xs));\n return stream_for_each(fun, stream_tail(xs));\n }\n}\nfunction stream_reverse(xs) {\n function rev(original, reversed) {\n return is_null(original) ? reversed : rev(stream_tail(original), pair(head(original), () => reversed));\n }\n return rev(xs, null);\n}\nfunction stream_append(xs, ys) {\n return is_null(xs) ? ys : pair(head(xs), () => stream_append(stream_tail(xs), ys));\n}\nfunction stream_member(x, s) {\n return is_null(s) ? null : head(s) === x ? s : stream_member(x, stream_tail(s));\n}\nfunction stream_remove(v, xs) {\n return is_null(xs) ? null : v === head(xs) ? stream_tail(xs) : pair(head(xs), () => stream_remove(v, stream_tail(xs)));\n}\nfunction stream_remove_all(v, xs) {\n return is_null(xs) ? null : v === head(xs) ? stream_remove_all(v, stream_tail(xs)) : pair(head(xs), () => stream_remove_all(v, stream_tail(xs)));\n}\nfunction stream_filter(p, s) {\n return is_null(s) ? null : p(head(s)) ? pair(head(s), () => stream_filter(p, stream_tail(s))) : stream_filter(p, stream_tail(s));\n}\nfunction enum_stream(start, end) {\n return start > end ? null : pair(start, () => enum_stream(start + 1, end));\n}\nfunction integers_from(n) {\n return pair(n, () => integers_from(n + 1));\n}\nfunction eval_stream(s, n) {\n function es(s, n) {\n return n === 1 ? list(head(s)) : pair(head(s), es(stream_tail(s), n - 1));\n }\n return n === 0 ? null : es(s, n);\n}\nfunction stream_ref(s, n) {\n return n === 0 ? head(s) : stream_ref(stream_tail(s), n - 1);\n}\nconst test=[x => x,2,3,[x => x],5];\nfunction f(x) {\n return f(x);\n}'
748
+ InfiniteLoopErrorType . NoBaseCase ,
749
+ false ,
750
+ 'The function f has encountered an infinite loop. It has no base case.' ,
751
+ [ code2 , code1 ]
733
752
)
734
753
. silentRun ( ) ;
735
754
} ) ;
736
755
} ) ;
756
+
757
+ test ( 'does not send correct data to sentry if approval is false' , ( ) => {
758
+ state = {
759
+ ...state ,
760
+ session : { ...state . session , experimentApproval : false , experimentCoinflip : true }
761
+ } ;
762
+ context = createContext ( 3 ) ;
763
+ const theCode = 'function f(x){f(x);} f(1);' ;
764
+
765
+ return expectSaga ( evalCode , theCode , context , execTime , workspaceLocation , actionType )
766
+ . withState ( state )
767
+ . not . call (
768
+ reportInfiniteLoopError ,
769
+ InfiniteLoopErrorType . NoBaseCase ,
770
+ false ,
771
+ 'The function f has encountered an infinite loop. It has no base case.' ,
772
+ [ theCode ]
773
+ )
774
+ . silentRun ( ) ;
775
+ } ) ;
776
+
777
+ test ( 'shows infinite loop error if coinflip is true' , ( ) => {
778
+ state = { ...state , session : { ...state . session , experimentCoinflip : true } } ;
779
+ const thisContext = createContext ( 3 ) ;
780
+ context = thisContext ;
781
+ const theCode = 'function f(x){f(x);} f(1);' ;
782
+
783
+ return expectSaga ( evalCode , theCode , context , execTime , workspaceLocation , actionType )
784
+ . withState ( state )
785
+ . silentRun ( )
786
+ . then ( result => {
787
+ const lastError = thisContext . errors [ thisContext . errors . length - 1 ] ;
788
+ expect ( lastError . explain ( ) ) . toContain ( 'no base case' ) ;
789
+ } ) ;
790
+ } ) ;
791
+
792
+ test ( 'does not show infinite loop error if coinflip is false' , ( ) => {
793
+ state = { ...state , session : { ...state . session , experimentCoinflip : false } } ;
794
+ const thisContext = createContext ( 3 ) ;
795
+ context = thisContext ;
796
+ const theCode = 'function f(x){f(x);} f(1);' ;
797
+
798
+ return expectSaga ( evalCode , theCode , context , execTime , workspaceLocation , actionType )
799
+ . withState ( state )
800
+ . silentRun ( )
801
+ . then ( result => {
802
+ const lastError = thisContext . errors [ thisContext . errors . length - 1 ] ;
803
+ expect ( lastError . explain ( ) ) . not . toContain ( 'no base case' ) ;
804
+ } ) ;
805
+ } ) ;
737
806
} ) ;
738
807
739
808
describe ( 'on DEBUG_RESUME action without interruptions or pausing' , ( ) => {
@@ -838,7 +907,8 @@ describe('evalTestCode', () => {
838
907
value = 'another test value' ;
839
908
options = {
840
909
scheduler : 'preemptive' ,
841
- originalMaxExecTime : 1000
910
+ originalMaxExecTime : 1000 ,
911
+ throwInfiniteLoops : true
842
912
} ;
843
913
index = 1 ;
844
914
type = TestcaseTypes . public ;
0 commit comments