Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/fugr/kernel_fugr_test.clas.testclasses.abap
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ CLASS ltcl_fugr DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT FINAL.
METHODS cexit_alpha_input_str FOR TESTING RAISING cx_static_check.
METHODS cexit_alpha_input_str_in FOR TESTING RAISING cx_static_check.
METHODS generate_sec_random FOR TESTING RAISING cx_static_check.
METHODS random_int_zero FOR TESTING RAISING cx_static_check.
METHODS random_int_one FOR TESTING RAISING cx_static_check.
METHODS random_int_six FOR TESTING RAISING cx_static_check.
METHODS random_int_negative FOR TESTING RAISING cx_static_check.
METHODS text_split1 FOR TESTING RAISING cx_static_check.
METHODS text_split2 FOR TESTING RAISING cx_static_check.
METHODS function_exists_yes FOR TESTING RAISING cx_static_check.
Expand All @@ -30,6 +34,86 @@ ENDCLASS.

CLASS ltcl_fugr IMPLEMENTATION.

METHOD random_int_zero.
* the span is RANGE + 1 values, so a range of zero has exactly one
DATA lv_random TYPE i.

CALL FUNCTION 'GENERAL_GET_RANDOM_INT'
EXPORTING
range = 0
IMPORTING
random = lv_random.

cl_abap_unit_assert=>assert_equals(
act = lv_random
exp = 0 ).
ENDMETHOD.

METHOD random_int_one.
* zero is a legal answer and the distinguishing one: a generator over
* 1..RANGE would never produce it. Two hundred draws of a fair coin land on
* each side at least once with a certainty no test needs to worry about.
DATA lv_random TYPE i.
DATA lv_zeroes TYPE i.
DATA lv_ones TYPE i.

DO 200 TIMES.
CALL FUNCTION 'GENERAL_GET_RANDOM_INT'
EXPORTING
range = 1
IMPORTING
random = lv_random.
CASE lv_random.
WHEN 0.
lv_zeroes = lv_zeroes + 1.
WHEN 1.
lv_ones = lv_ones + 1.
WHEN OTHERS.
cl_abap_unit_assert=>fail( msg = |out of range: { lv_random }| ).
ENDCASE.
ENDDO.

cl_abap_unit_assert=>assert_differs(
act = lv_zeroes
exp = 0
msg = 'zero must be reachable' ).
cl_abap_unit_assert=>assert_differs(
act = lv_ones
exp = 0
msg = 'the range itself must be reachable' ).
ENDMETHOD.

METHOD random_int_six.
DATA lv_random TYPE i.

DO 200 TIMES.
CALL FUNCTION 'GENERAL_GET_RANDOM_INT'
EXPORTING
range = 6
IMPORTING
random = lv_random.
IF lv_random < 0 OR lv_random > 6.
cl_abap_unit_assert=>fail( msg = |out of range: { lv_random }| ).
ENDIF.
ENDDO.
ENDMETHOD.

METHOD random_int_negative.
* a negative range is not an error: the span runs towards RANGE
DATA lv_random TYPE i.

DO 200 TIMES.
CALL FUNCTION 'GENERAL_GET_RANDOM_INT'
EXPORTING
range = -5
IMPORTING
random = lv_random.
IF lv_random < -5 OR lv_random > 0.
cl_abap_unit_assert=>fail( msg = |out of range: { lv_random }| ).
ENDIF.
ENDDO.
ENDMETHOD.

METHOD text_split1.
CONSTANTS lc_text TYPE c LENGTH 200 VALUE '01234567890123456789012345678901234567890123456789 123456789'.
DATA lv_line TYPE string.
Expand Down
31 changes: 31 additions & 0 deletions src/fugr/openabap.fugr.general_get_random_int.abap
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
FUNCTION general_get_random_int.

* A uniformly distributed integer between zero and RANGE, both included.
*
* Measured against an AS ABAP 1909 rather than inferred, because the obvious
* reading of the parameter text ("The random int will be <= range") is one to
* range, and that is wrong. Three hundred calls per range:
*
* range 1 values 0..1 zero in 163 (a half)
* range 2 values 0..2 zero in 105 (a third)
* range 6 values 0..6 zero in 53 (a seventh)
* range 100 values 0..100 zero in 3 (a hundred-and-first)
*
* The span is RANGE + 1 values. A range of zero answers zero, and a negative
* range is not an error: -5 answered -2, so the span runs towards RANGE on
* whichever side of zero it lies. The module declares no exceptions and the
* real one raises none.
*
* Written against Math.random directly rather than through
* cl_abap_random_int, which was the first attempt. That class cannot express
* this: cl_abap_random=>intinrange opens with ASSERT high > low and
* ASSERT low >= 0, so a range of zero dumps and every negative range dumps.
* Reaching the contract through abs( ) and a negation would work and would
* read as cleverness hiding the intent. The randomness is the same
* Math.random either way, since that is what cl_abap_random uses too, and
* generate_sec_random in this group is the precedent for the plain form.
WRITE '@KERNEL const r = range.get();'.
WRITE '@KERNEL const span = Math.abs(r) + 1;'.
WRITE '@KERNEL random.set(Math.sign(r) * Math.floor(Math.random() * span));'.

ENDFUNCTION.
17 changes: 17 additions & 0 deletions src/fugr/openabap.fugr.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,23 @@
</RSFDO>
</DOCUMENTATION>
</item>
<item>
<FUNCNAME>GENERAL_GET_RANDOM_INT</FUNCNAME>
<IMPORT>
<RSIMP>
<PARAMETER>RANGE</PARAMETER>
<REFERENCE>X</REFERENCE>
<TYP>I</TYP>
</RSIMP>
</IMPORT>
<EXPORT>
<RSEXP>
<PARAMETER>RANDOM</PARAMETER>
<REFERENCE>X</REFERENCE>
<TYP>I</TYP>
</RSEXP>
</EXPORT>
</item>
<item>
<FUNCNAME>GENERATE_SEC_RANDOM</FUNCNAME>
<IMPORT>
Expand Down