@@ -28,7 +28,7 @@ import {
2828import { Library , Testcase , TestcaseType , TestcaseTypes } from '../../assessment/AssessmentTypes' ;
2929import { mockRuntimeContext } from '../../mocks/ContextMocks' ;
3030import { mockTestcases } from '../../mocks/GradingMocks' ;
31- import { reportInfiniteLoopError } from '../../utils/InfiniteLoopReporter' ;
31+ import { InfiniteLoopErrorType , reportInfiniteLoopError } from '../../utils/InfiniteLoopReporter' ;
3232import { showSuccessMessage , showWarningMessage } from '../../utils/NotificationsHelper' ;
3333import {
3434 beginClearContext ,
@@ -64,6 +64,11 @@ function generateDefaultState(
6464) : OverallState {
6565 return {
6666 ...defaultState ,
67+ session : {
68+ ...defaultState . session ,
69+ experimentApproval : false ,
70+ experimentCoinflip : true
71+ } ,
6772 workspaces : {
6873 ...defaultState . workspaces ,
6974 [ workspaceLocation ] : {
@@ -130,7 +135,8 @@ describe('EVAL_EDITOR', () => {
130135 scheduler : 'preemptive' ,
131136 originalMaxExecTime : execTime ,
132137 stepLimit : 1000 ,
133- useSubst : false
138+ useSubst : false ,
139+ throwInfiniteLoops : true
134140 }
135141 ]
136142 } )
@@ -148,7 +154,8 @@ describe('EVAL_EDITOR', () => {
148154 scheduler : 'preemptive' ,
149155 originalMaxExecTime : execTime ,
150156 stepLimit : 1000 ,
151- useSubst : false
157+ useSubst : false ,
158+ throwInfiniteLoops : true
152159 }
153160 ]
154161 } )
@@ -212,7 +219,8 @@ describe('EVAL_REPL', () => {
212219 scheduler : 'preemptive' ,
213220 originalMaxExecTime : 1000 ,
214221 stepLimit : 1000 ,
215- useSubst : false
222+ useSubst : false ,
223+ throwInfiniteLoops : true
216224 } )
217225 . dispatch ( {
218226 type : EVAL_REPL ,
@@ -641,7 +649,8 @@ describe('evalCode', () => {
641649 scheduler : 'preemptive' ,
642650 originalMaxExecTime : 1000 ,
643651 stepLimit : 1000 ,
644- useSubst : false
652+ useSubst : false ,
653+ throwInfiniteLoops : true
645654 } ;
646655 lastDebuggerResult = { status : 'error' } ;
647656 state = generateDefaultState ( workspaceLocation ) ;
@@ -656,7 +665,8 @@ describe('evalCode', () => {
656665 scheduler : 'preemptive' ,
657666 originalMaxExecTime : execTime ,
658667 stepLimit : 1000 ,
659- useSubst : false
668+ useSubst : false ,
669+ throwInfiniteLoops : true
660670 } )
661671 . put ( evalInterpreterSuccess ( value , workspaceLocation ) )
662672 . silentRun ( ) ;
@@ -670,7 +680,8 @@ describe('evalCode', () => {
670680 scheduler : 'preemptive' ,
671681 originalMaxExecTime : execTime ,
672682 stepLimit : 1000 ,
673- useSubst : false
683+ useSubst : false ,
684+ throwInfiniteLoops : true
674685 } )
675686 . put ( endDebuggerPause ( workspaceLocation ) )
676687 . put ( evalInterpreterSuccess ( 'Breakpoint hit!' , workspaceLocation ) )
@@ -684,7 +695,8 @@ describe('evalCode', () => {
684695 scheduler : 'preemptive' ,
685696 originalMaxExecTime : execTime ,
686697 stepLimit : 1000 ,
687- useSubst : false
698+ useSubst : false ,
699+ throwInfiniteLoops : true
688700 } )
689701 . put . like ( { action : { type : EVAL_INTERPRETER_ERROR } } )
690702 . silentRun ( ) ;
@@ -707,17 +719,22 @@ describe('evalCode', () => {
707719 scheduler : 'preemptive' ,
708720 originalMaxExecTime : execTime ,
709721 stepLimit : 1000 ,
710- useSubst : false
722+ useSubst : false ,
723+ throwInfiniteLoops : true
711724 } )
712725 . put ( evalInterpreterError ( context . errors , workspaceLocation ) )
713726 . silentRun ( ) ;
714727 } ) ;
715728
716729 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);}' ;
719737 const code2 = 'f(1);' ;
720- state = generateDefaultState ( workspaceLocation , { } ) ;
721738
722739 return runInContext ( code1 , context , {
723740 scheduler : 'preemptive' ,
@@ -728,12 +745,64 @@ describe('evalCode', () => {
728745 . withState ( state )
729746 . call (
730747 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 ]
733752 )
734753 . silentRun ( ) ;
735754 } ) ;
736755 } ) ;
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+ } ) ;
737806 } ) ;
738807
739808 describe ( 'on DEBUG_RESUME action without interruptions or pausing' , ( ) => {
@@ -838,7 +907,8 @@ describe('evalTestCode', () => {
838907 value = 'another test value' ;
839908 options = {
840909 scheduler : 'preemptive' ,
841- originalMaxExecTime : 1000
910+ originalMaxExecTime : 1000 ,
911+ throwInfiniteLoops : true
842912 } ;
843913 index = 1 ;
844914 type = TestcaseTypes . public ;
0 commit comments