Skip to content

Commit

Permalink
Convert Stack_Size_Spec to (regression) test
Browse files Browse the repository at this point in the history
  • Loading branch information
Akirathan committed Oct 23, 2024
1 parent 847c496 commit 990ee6a
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 50 deletions.
2 changes: 2 additions & 0 deletions test/Base_Tests/src/Main.enso
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import project.Runtime.Missing_Required_Arguments_Spec
import project.Runtime.Ref_Spec
import project.Runtime.State_Spec
import project.Runtime.Stack_Traces_Spec
import project.Runtime.Stack_Size_Spec

import project.System.Environment_Spec
import project.System.File_Spec
Expand Down Expand Up @@ -160,6 +161,7 @@ main filter=Nothing =
Missing_Required_Arguments_Spec.add_specs suite_builder
Lazy_Generator_Spec.add_specs suite_builder
Stack_Traces_Spec.add_specs suite_builder
Stack_Size_Spec.add_specs suite_builder
Text_Spec.add_specs suite_builder
Time_Spec.add_specs suite_builder
URI_Spec.add_specs suite_builder
Expand Down
104 changes: 54 additions & 50 deletions test/Base_Tests/src/Runtime/Stack_Size_Spec.enso
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
# Tests regression of the overall stack trace size when calling nested
# Vector.map. It is tested by invoking a subprocess with smaller thread
# stack size (`-Xss` cmdline option).
# `Vector.map`. It is tested by invoking a subprocess on a generated code
# that contains `n` nested `Vector.map` calls.
# The subprocess has Truffle compiler disabled with `-Dpolyglot.engine.Compiler=false`
# to ensure there are no (Java) stack frames dropped. Moreover, we
# set explicitly `-XX:MaxJavaStackTraceDepth=...` for the subprocess to overcome
# the default length (1024) of `RuntimeException.getStackTrace` which is too low.
#
# The test runs two subprocesses with different nesting and computes the
# difference of Java stack sizes. This difference must not exceed certain limit.


private

from Standard.Base import all
import Standard.Base.Runtime.Ref.Ref
import Standard.Base.System.Process.Process_Builder.Process_Result

from Standard.Test import all


## Find the Enso binary under the `built-distribution` directory
enso_bin -> File =
Expand All @@ -34,21 +44,24 @@ enso_bin -> File =

## Generates code for mapping over a vector with the given nesting level.
Returns code of the main method that is meant to be pasted into a separate module.
The code prints the count of Java frames to stdout in the deepest `Vector.map` call.

Example of the code is (for nesting_level 2):
```
main =
vec = [[42]]
vec.map e0->
e0.map e1->
e1 + 1
cnt = RuntimeException.new.getStackTrace.length
IO.println 'java_stack_frames='+cnt.to_text
```

Arguments:
- nesting_level How many times should the vector be nested
generate_code nesting_level:Integer -> Text =
bldr = Vector.Builder.new
bldr.append "from Standard.Base import all"
bldr.append "polyglot java import java.lang.RuntimeException"
bldr.append '\n'
bldr.append <| "main = "
bldr.append <| " "
Expand All @@ -66,64 +79,55 @@ generate_code nesting_level:Integer -> Text =
+ (i + 1).to_text
+ "-> "
bldr.append <| (" " * (nesting_level + 1))
+ "e"
+ (nesting_level - 1).to_text
+ " + 1"
+ "cnt = RuntimeException.new.getStackTrace.length"
bldr.append <| (" " * (nesting_level + 1))
+ "IO.println <| 'java_stack_frames=' + cnt.to_text"
+ '\n'
vec = bldr.to_vector
vec.reduce \first_line:Text second_line:Text ->
bldr.to_vector.reduce \first_line:Text second_line:Text ->
first_line + '\n' + second_line


run_with_stack_size stack_size:Text enso_args:Vector -> Process_Result =
java_opts = "-Xss" + stack_size
+ " "
+ "-Dpolyglot.engine.Compilation=false"
## Runs Enso subprocess with disabled Truffle compiler, with
larger thread stack and also with larger stack trace element collected
(which is needed for `new RuntimeException().getStackTrace().length`)
as this value is by default set only to 1024.

The thread stack size is also set to a sufficiently large value
to ensure there is no StackOverflow.
run_without_compiler enso_args:Vector -> Process_Result =
java_opts = "-Dpolyglot.engine.Compilation=false "
+ "-XX:MaxJavaStackTraceDepth=18000 "
+ "-Xms16M"
args = ["JAVA_OPTS="+java_opts, enso_bin.path] + enso_args
Process.run "env" (args + enso_args)


failed_with_stack_overflow res:Process_Result -> Boolean =
case res.exit_code of
Exit_Code.Success -> False
Exit_Code.Failure _ ->
first_line = res.stdout.split '\n' . first
first_line.contains "Stack overflow"

## Runs enso as a subprocess with specified stack size and nesting level of Vector.map
Returns False if the process failed with StackOverflowError, True if it succeeded.
## Runs enso as a subprocess with the specified nesting level of `Vector.map` calls.
Returns count of Java stack frames from the deepest `Vector.map` call.

Arguments:
- nesting Level of nesting of `Vector.map` method calls.
- stack_size Size of the stack, passed to `-Xss` cmd line opt.
run nesting:Integer stack_size:Text -> Boolean =
run nesting:Integer -> Integer =
tmp_file = File.create_temporary_file suffix=".enso"
code = generate_code nesting
code.write tmp_file
proc_res = run_with_stack_size stack_size ["--run", tmp_file.path]
failed_with_stack_overflow proc_res . not


run_all =
stack_sizes = ["256k"]
nestings = 1.up_to 20 . to_vector
stack_sizes.each \stack_size ->
so_encountered = Ref.new False
nestings.each \nesting ->
header_msg = "{nesting: " + nesting.to_text + ", stack_size: " + stack_size.to_text + "} "
case so_encountered.get of
True ->
IO.println <| header_msg + "SKIPPED (SO already encountered with the same stack size in lower nesting levels)"
False ->
result = run nesting stack_size
case result of
False ->
IO.println <| header_msg + "FAILED with StackOverflow"
# It is certain that the rest of nesting levels will fail now
so_encountered.put True
True ->
IO.println <| header_msg + "SUCCEEDED"

# TODO: Convert this to test - we will require at least, e.g., 12 nesting levels to succeed with stack 256k
main =
run_all
proc_res = run_without_compiler ["--run", tmp_file.path]
# FInd and parse a specific line from the process stdout
j_frames_line = proc_res.stdout.split '\n' . find \line ->
line.contains "java_stack_frames"
j_frames_line.split '=' . last . parse_integer


add_specs suite_builder =
suite_builder.group "Stack size" \group_builder ->
group_builder.specify "Java stack size of nested Vector.map should be kept reasonably low" <|
nesting_10 = run 10
nesting_11 = run 11
stack_size = nesting_11 - nesting_10
(stack_size < 115) . should_be_true


main filter=Nothing =
suite = Test.build \suite_builder ->
add_specs suite_builder
suite.run_with_filter filter

0 comments on commit 990ee6a

Please sign in to comment.