Repository: goby-lang/goby
Pull request #819 (2020-01-15 00:41:37)

Add random number generation by mapping rand() to math/rand
We're using `math/rand`, because when we're later implementing `srand()`, we need it to support seeding, which `crypto/rand` does not provide.

Fixes https://github.com/goby-lang/goby/issues/810

codecov[bot]: # [Codecov](https://codecov.io/gh/goby-lang/goby/pull/819?src=pr&el=h1) Report
> Merging [#819](https://codecov.io/gh/goby-lang/goby/pull/819?src=pr&el=desc) into [master](https://codecov.io/gh/goby-lang/goby/commit/fd1ea625b30c0dd818d480ff582cc6fc461d304e?src=pr&el=desc) will **increase** coverage by `0.02%`.
> The diff coverage is `81.81%`.

[![Impacted file tree graph](https://codecov.io/gh/goby-lang/goby/pull/819/graphs/tree.svg?width=650&token=giIKD13jMt&height=150&src=pr)](https://codecov.io/gh/goby-lang/goby/pull/819?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #819      +/-   ##
==========================================
+ Coverage   80.85%   80.88%   +0.02%     
==========================================
  Files          54       54              
  Lines        7407     7418      +11     
==========================================
+ Hits         5989     6000      +11     
  Misses       1193     1193              
  Partials      225      225
```


| [Impacted Files](https://codecov.io/gh/goby-lang/goby/pull/819?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [vm/class.go](https://codecov.io/gh/goby-lang/goby/pull/819/diff?src=pr&el=tree#diff-dm0vY2xhc3MuZ28=) | `85.77% <81.81%> (-0.07%)` | :arrow_down: |
| [vm/integer.go](https://codecov.io/gh/goby-lang/goby/pull/819/diff?src=pr&el=tree#diff-dm0vaW50ZWdlci5nbw==) | `65.03% <0%> (+0.65%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goby-lang/goby/pull/819?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goby-lang/goby/pull/819?src=pr&el=footer). Last update [fd1ea62...9a09ecb](https://codecov.io/gh/goby-lang/goby/pull/819?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/vm/class.go b/vm/class.go
index f2cb629..4014152 100644
--- a/vm/class.go
+++ b/vm/class.go
@@ -7,6 +7,7 @@ import (
 	"sync"
 	"time"
 
+	"math/rand"
 	"sort"
 
 	"github.com/goby-lang/goby/vm/classes"
@@ -1337,6 +1338,39 @@ var builtinClassCommonInstanceMethods = []*BuiltinMethodObject{
 
 		},
 	},
+	{
+		Name: "rand",
+		Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
+			aLen := len(args)
+
+			switch aLen {
+			case 0:
+				return t.vm.initFloatObject(rand.Float64())
+			case 1:
+				_, ok := args[0].(*IntegerObject)
+
+				if !ok {
+					return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name)
+				}
+				return t.vm.InitIntegerObject(rand.Intn(args[0].Value().(int)))
+			case 2:
+				_, minOk := args[0].(*IntegerObject)
+
+				if !minOk {
+					return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name)
+				}
+				_, maxOk := args[1].(*IntegerObject)
+
+				if !maxOk {
+					return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[1].Class().Name)
+				}
+
+				return t.vm.InitIntegerObject(rand.Intn(args[1].Value().(int)-args[0].Value().(int)+1) + args[0].Value().(int))
+			default:
+				return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, aLen)
+			}
+		},
+	},
 	{
 		// A predicate class method that returns `true` if the object has an ability to respond to the method, otherwise `false`.
 		// Note that signs like `+` or `?` should be String literal.


Test patch:
diff --git a/vm/class_test.go b/vm/class_test.go
index 33f0d30..5758ab6 100644
--- a/vm/class_test.go
+++ b/vm/class_test.go
@@ -985,7 +985,7 @@ func TestInheritsMethodMissingMethod(t *testing.T) {
 		{`
 		class Bar
 		end
-		
+
 		Bar.new.inherits_method_missing?
 
 `, false},
@@ -993,7 +993,7 @@ func TestInheritsMethodMissingMethod(t *testing.T) {
 		class Bar
 		  inherits_method_missing
 		end
-		
+
 		Bar.new.inherits_method_missing?
 
 `, true},
@@ -1132,6 +1132,43 @@ func TestRaiseMethodFail(t *testing.T) {
 	}
 }
 
+func TestRandMethod(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected string
+	}{
+		{`rand.class.name`, "Float"},
+		{`rand(1).class.name`, "Integer"},
+		{`rand(1, 2).class.name`, "Integer"},
+		{`(60 < rand(64, 66)).to_s`, "true"},
+		{`(70 > rand(64, 66)).to_s`, "true"},
+	}
+	for i, tt := range tests {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		VerifyExpected(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, 0)
+		v.checkSP(t, i, 1)
+	}
+}
+
+func TestRandMethodFail(t *testing.T) {
+	testsFail := []errorTestCase{
+		{`rand(1, 10, 100)`, "ArgumentError: Expect 2 argument(s). got: 3", 1},
+		{`rand("some string")`, "TypeError: Expect argument to be Integer. got: String", 1},
+		{`rand("some string", "some other string")`, "TypeError: Expect argument to be Integer. got: String", 1},
+		{`rand(10, "some other string")`, "TypeError: Expect argument to be Integer. got: String", 1},
+	}
+
+	for i, tt := range testsFail {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		checkErrorMsg(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, tt.expectedCFP)
+		v.checkSP(t, i, 1)
+	}
+}
+
 func TestRespondToMethod(t *testing.T) {
 	tests := []struct {
 		input    string
@@ -1319,7 +1356,7 @@ func TestSendMethod(t *testing.T) {
 		    10
 		  end
 		end
-		
+
 		Foo.new.send(:bar)
 		`, 10},
 	}
@@ -1651,7 +1688,7 @@ func TestClassSingletonClassMethod(t *testing.T) {
 		`, "#<Class:Bar>"},
 		{`
 		module Bar; end
-		
+
 		Bar.singleton_class.name
 		`, "#<Class:Bar>"},
 		// Check if this works on non-class objects
@@ -1663,12 +1700,12 @@ func TestClassSingletonClassMethod(t *testing.T) {
 		// Below is for testing module inheritance chain
 		{`
 		module Bar; end
-		
+
 		Bar.singleton_class.class.name
 		`, "Class"},
 		{`
 		module Bar; end
-		
+
 		Bar.singleton_class.superclass.name
 		`, "Module"},
 	}