-
Notifications
You must be signed in to change notification settings - Fork 646
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as unit tests go, it should be pretty straightforward to stub out os.Stdin with a different File object and then read from that object. See the Print() tests to understand what I mean.
Re: performance: My guess is that go run is compiling a bunch of stuff before actually running the program. go run and go test don't do much caching of compiled artifacts between runs so that's probably what's causing the delay.
runtime/builtin_types.go
Outdated
} | ||
|
||
fin := os.Stdin | ||
fout := os.Stdout |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than writing to Stdout directly, you could use the Print() function: Print(f, args, false)
runtime/builtin_types.go
Outdated
prompt, _ := ToStr(f, args[0]) | ||
fmt.Fprint(fout, prompt.Value()) | ||
} | ||
s, _, _ := in.ReadLine() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ReadLine() isn't ideal here because it can return with less than a full line. I think the right thing to do is probably use the grumpy.File object representing sys.stdin and call readLine() on it. But there's no clear way to get the sys module from here.
One possible solution that would be to create Stdin, Stdout and Stderr variables in the grumpy package that wrap os.Stdin/out/err with a grumpy.File object, and then have the sys module expose those instead of creating new files. The grumpy.Print() function could then use the new Stdout object. This isn't perfect but I think it is an improvement over what we have today.
runtime/builtin_types.go
Outdated
s, _, _ := in.ReadLine() | ||
|
||
// Todo: Should implement more cases of error. | ||
pySsizeTmax := ((^uint(0)) - 1) >> 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that if len(s) were ever > than this value that Go would panic so don't bother checking this.
runtime/builtin_types.go
Outdated
return nil, f.RaiseType(OverflowErrorType, msg) | ||
} | ||
|
||
return NewStr(string(s[0:size])).ToObject(), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this different than NewStr(s)
?
@trotterdylan Thank you for great and awesome review. I will try to fix it. |
@trotterdylan Updated! I fixed codes as you commented. Actually, about using grumpy.File object is not 100% same as you intended. I found the simpler way. PTAL |
runtime/builtin_types.go
Outdated
pyPrint(f, args, "", "", stdout) | ||
} | ||
|
||
line, _ := stdin.readLine(MaxInt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I was wrong about implementing it this way :(
The trouble is that the C implementation uses fgets on the underlying C file pointer. fgets reads until it reaches \n
but readLine will stop when it reaches \r
and will convert that to a \n
.
Probably the simplest thing to do here is to call stdin.reader.ReadString("\n")
instead. This would mean you'd have to handle the EOF error case, however.
runtime/builtin_types.go
Outdated
pyPrint(f, args, "", "", stdout) | ||
} | ||
|
||
line, _ := stdin.readLine(MaxInt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You must handle the error case. It's pretty simple to do:
if err != nil {
return nil, f.RaiseType(IOErrorType, err.Error())
}
runtime/builtin_types.go
Outdated
} | ||
|
||
stdout := os.Stdout | ||
stdin := NewFileFromFD(os.Stdin.Fd()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trouble with creating a new File wrapping os.Stdin is that it will have a different buffered reader than the one held by sys.stdin. This is especially problematic because this File object is cleaned up after builtinRawInput() returns, meaning that whatever buffered data it held just disappears.
This goes back to my suggestion of having grumpy.Stdin/out/err variables that get used by this function and having sys.stdin/out/err be aliases for those variables:
# sys.py
from __go__.grumpy import Stdin as stdin, Stdout as stdout, Stderr as stderr
This way any buffering done for one call will be used by subsequent calls.
runtime/builtin_types_test.go
Outdated
@@ -422,3 +422,24 @@ func TestBuiltinSetAttr(t *testing.T) { | |||
} | |||
} | |||
} | |||
|
|||
// TODO: Improvment unit tests codes into catch user's input and output. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd want to see some additional tests that exercise the whole builtinRawInput() function before I approve this PR. It's important that new code is fully tested because the probability that somebody retroactively adds tests is low.
@trotterdylan A lot of stuff!! I will do it as your comment. Thanks |
@trotterdylan Updated!! Please take a look. |
runtime/builtin_types.go
Outdated
return nil, f.RaiseType(TypeErrorType, msg) | ||
} | ||
|
||
osStdout := os.Stdout |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the new grumpy.Stdout variable for output. This will require modifying pyPrint(), Print(), etc. to take a *grumpy.File instead of *os.File. Might be better to add Stdin/out/err and change the print behavior in a separate PR and once that's merged, continue with this one.
runtime/builtin_types_test.go
Outdated
// Create a fake Stdin for input test. | ||
stdinFile, _ := ioutil.TempFile(os.TempDir(), "prefix") | ||
stdinFile.Write([]byte("hello grumpy\n")) | ||
stdinFile.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need to reopen stdinFile. You should be able to read what you've written. You may need to Sync() (and possibly Seek() to the beginning, but I don't think so).
runtime/builtin_types_test.go
Outdated
func TestRawInput(t *testing.T) { | ||
fun := newBuiltinFunction("TestRawInput", func(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { | ||
// Create a fake Stdin for input test. | ||
stdinFile, _ := ioutil.TempFile(os.TempDir(), "prefix") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to check errors for these io operations.
runtime/builtin_types_test.go
Outdated
func TestRawInput(t *testing.T) { | ||
fun := newBuiltinFunction("TestRawInput", func(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { | ||
// Create a fake Stdin for input test. | ||
stdinFile, _ := ioutil.TempFile(os.TempDir(), "prefix") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it'd be easier, but you could also use a Pipe() instead of a temp file for stdin, similar to what you've done for stdout. Something like this:
stdinFile, w := os.Pipe()
go func() {
w.Write([]byte("hello grumpy\n"))
w.Close()
}()
You need to do the write asynchronously this way in case the Write() call blocks waiting for a Read() on the other end of the pipe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool~! I will try it!!
runtime/builtin_types_test.go
Outdated
os.Stdout = oldStdout | ||
Stdin = oldStdin | ||
stdinFile.Close() | ||
os.Remove(stdinFile.Name()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move defer os.Remove(stdinFile.Name())
up to where you create the file so that an early return won't leave the tempfile lying around.
runtime/builtin_types_test.go
Outdated
if raised != nil { | ||
return nil, raised | ||
} | ||
result := newTestTuple(input, output.String()).ToObject() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return newTestTuple(input, output.String()).ToObject(), nil
@trotterdylan Updated. PTAL |
runtime/builtin_types.go
Outdated
} | ||
|
||
if len(args) == 1 { | ||
pyPrint(f, args, "", "", Stdout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check the resulting exception
runtime/builtin_types_test.go
Outdated
} | ||
|
||
go func() { | ||
defer w.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't bother defering, just Close after the Write
runtime/builtin_types_test.go
Outdated
go func() { | ||
defer w.Close() | ||
w.Write([]byte("hello grumpy\n")) | ||
stdinFile.Sync() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's w that needs to Sync, but closing it will do the same thing.
runtime/builtin_types_test.go
Outdated
}() | ||
|
||
// Create a fake Stdout for output test. | ||
r, stdoutFile, err := os.Pipe() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any way we can use captureStdout for this part?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@trotterdylan I want to do by the same way you mentioned. But in my opinion, until we refactoring captureStdout
, I think not possible right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we could change this part for general functions. It might be possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What specifically needs to be generalized? Any inputs or outputs can made part of the closure for the passed function and read or written inside the function call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@trotterdylan In this case, builtinRawInput()
will return *Object
and *BaseException
, but fn
in captureStdout
want to return only *BaseException
. It will works for any function I will pass?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm probably oversimplifying, but would something like this work?
var input *Object
raised := captureStdout(func() *BaseException {
input = builtinRawInput(...)
})
if raised != nil {
...
}
// validate input
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@trotterdylan Awesome! You are right!! Thank you for guiding me. 👍
runtime/builtin_types_test.go
Outdated
var raised *BaseException | ||
|
||
go func() { | ||
defer close(done) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to defer either of these, just call them normally below
runtime/builtin_types_test.go
Outdated
}() | ||
done := make(chan struct{}) | ||
var input *Object | ||
var output bytes.Buffer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declare this where it's used down below when passed to Copy
@trotterdylan Updated. p.s Can I get good materials to know when to use |
@corona10 re: defer: It can be used like finally. But, like finally, it's only really useful if there are multiple possible paths for exiting the function or an exception (panic in Go) is raised. In this case, a panic is a fatal error so it doesn't really matter if we close things. The only time it's really important to run some code in the case of panic is if the panic may be handled somewhere (for the most part in the Grumpy code, we assume panic is fatal) or if you need to release some resource even in the event of a panic. Sometimes mutex falls into this category because it can otherwise cause a deadlock if not released. It's also good to guarantee that temp files are cleaned up. But otherwise, the possibility of panic is not usually a good reason to defer. |
@trotterdylan Thank you for review and great guide! I updated it and please take a look. Thanks to you, I learned about how Go works. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great. The main thing now is to add a few more test cases.
runtime/builtin_types_test.go
Outdated
|
||
go func() { | ||
w.Write([]byte("hello grumpy\n")) | ||
w.Sync() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want to close w here since that's the end of stdin.
runtime/builtin_types_test.go
Outdated
}).ToObject() | ||
cases := []invokeTestCase{ | ||
{args: wrapArgs(), want: newTestTuple("hello grumpy", "").ToObject()}, | ||
{args: wrapArgs("abc"), want: newTestTuple("hello grumpy", "abc").ToObject()}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's a few more things that need testing:
- Multiline prompt parameter: make sure all string lines are printed
- Multiple lines on stdin: only the first line should be returned by the raw_input call
- An empty stdin should raise EOFError:
$ python -c 'raw_input()' < /dev/null
Traceback (most recent call last):
File "<string>", line 1, in <module>
EOFError: EOF when reading a line
The last two test cases can be supported by having fun
take a parameter that is the string to put on stdin.
1) Add more testcases. 2) Close w since that's the end of stdin. 3) Add EOFErrorType.
@trotterdylan Updated!! PTAL |
runtime/builtin_types_test.go
Outdated
@@ -425,6 +425,69 @@ func TestBuiltinSetAttr(t *testing.T) { | |||
} | |||
} | |||
|
|||
func TestRawInput(t *testing.T) { | |||
inputTest := func(s string, cases []invokeTestCase) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This organization seems like a reasonable way to support an additional parameter, but it's a little unorthodox. The way I've usually done it is something like this:
fun := wrapFuncForTest(func(f *Frame, s string, args ...*Object) (*Object, *BaseException) {
...
go func() {
w.Write([]byte(s))
w.Close()
}()
...
in, raised := builtinRawInput(f, args, nil)
...
})
...
{args: wrapArgs("hello\n", "show"), want: newTestTuple("hello", "show").ToObject()},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great! Thanks for all the iterations. I'm really happy with the end result. Merging.
@trotterdylan Thank you for reviewing long days :-) |
@corona10 very nice :) |
I implemented a simplified version of raw_input().
Please review on focusing these.
First, It might be not 100% compatible with CPython and pypy's raw_input().
and also need more implementation to handle error cases.
Second, I need some guide for writing unit test codes to check it is well working for raw_input().
I don't know how to test user's input from keyboard.
Third, prompt printing is slower when running with
go run *.go
so if you have solutions then please give me a feedback.Related PR= #247