@@ -74,7 +74,7 @@ func TestEvaluate(t *testing.T) {
7474 Output string
7575 }{
7676 {[]string {
77- " import \ " fmt\" " ,
77+ ` import "fmt"` ,
7878 "a := 1" ,
7979 "fmt.Println(a)" ,
8080 }, "1\n " },
@@ -86,7 +86,7 @@ func TestEvaluate(t *testing.T) {
8686 "func myFunc(x int) int {" ,
8787 " return x+1" ,
8888 "}" ,
89- " fmt.Println(\ " func defined\" )" ,
89+ ` fmt.Println("func defined")` ,
9090 }, "func defined\n " },
9191 {[]string {
9292 "b := myFunc(1)" ,
@@ -146,7 +146,7 @@ func TestPanicGeneratesError(t *testing.T) {
146146 client , closeClient := newTestJupyterClient (t )
147147 defer closeClient ()
148148
149- content , pub := client .executeCode (t , `panic("error"` )
149+ content , pub := client .executeCode (t , `panic("error") ` )
150150
151151 status := getString (t , "content" , content , "status" )
152152
@@ -167,6 +167,111 @@ func TestPanicGeneratesError(t *testing.T) {
167167 }
168168}
169169
170+ // TestPrintStdout tests that data written to stdout publishes the same data in a "stdout" "stream" message.
171+ func TestPrintStdout (t * testing.T ) {
172+ cases := []struct {
173+ Input []string
174+ Output []string
175+ }{
176+ {[]string {
177+ `import "fmt"` ,
178+ "a := 1" ,
179+ "fmt.Println(a)" ,
180+ }, []string {"1\n " }},
181+ {[]string {
182+ "a = 2" ,
183+ "fmt.Print(a)" ,
184+ }, []string {"2" }},
185+ {[]string {
186+ `import "os"` ,
187+ `os.Stdout.WriteString("3")` ,
188+ }, []string {"3" }},
189+ {[]string {
190+ `fmt.Fprintf(os.Stdout, "%d\n", 4)` ,
191+ }, []string {"4\n " }},
192+ {[]string {
193+ `import "time"` ,
194+ "for i := 0; i < 3; i++ {" ,
195+ " fmt.Println(i)" ,
196+ " time.Sleep(500 * time.Millisecond)" , // Stall to prevent prints from buffering into single message.
197+ "}" ,
198+ }, []string {"0\n " , "1\n " , "2\n " }},
199+ }
200+
201+ t .Logf ("Should produce stdout stream messages when writing to stdout" )
202+
203+ cases:
204+ for k , tc := range cases {
205+ // Give a progress report.
206+ t .Logf (" Evaluating code snippet %d/%d." , k + 1 , len (cases ))
207+
208+ // Get the result.
209+ stdout , _ := testOutputStream (t , strings .Join (tc .Input , "\n " ))
210+
211+ // Compare the result.
212+ if len (stdout ) != len (tc .Output ) {
213+ t .Errorf ("\t %s Test case expected %d message(s) on stdout but got %d." , failure , len (tc .Output ), len (stdout ))
214+ continue
215+ }
216+ for i , expected := range tc .Output {
217+ if stdout [i ] != expected {
218+ t .Errorf ("\t %s Test case returned unexpected messages on stdout." , failure )
219+ continue cases
220+ }
221+ }
222+ t .Logf ("\t %s Returned the expected messages on stdout." , success )
223+ }
224+ }
225+
226+ // TestPrintStderr tests that data written to stderr publishes the same data in a "stderr" "stream" message.
227+ func TestPrintStderr (t * testing.T ) {
228+ cases := []struct {
229+ Input []string
230+ Output []string
231+ }{
232+ {[]string {
233+ `import "fmt"` ,
234+ `import "os"` ,
235+ "a := 1" ,
236+ "fmt.Fprintln(os.Stderr, a)" ,
237+ }, []string {"1\n " }},
238+ {[]string {
239+ `os.Stderr.WriteString("2")` ,
240+ }, []string {"2" }},
241+ {[]string {
242+ `import "time"` ,
243+ "for i := 0; i < 3; i++ {" ,
244+ " fmt.Fprintln(os.Stderr, i)" ,
245+ " time.Sleep(500 * time.Millisecond)" , // Stall to prevent prints from buffering into single message.
246+ "}" ,
247+ }, []string {"0\n " , "1\n " , "2\n " }},
248+ }
249+
250+ t .Logf ("Should produce stderr stream messages when writing to stderr" )
251+
252+ cases:
253+ for k , tc := range cases {
254+ // Give a progress report.
255+ t .Logf (" Evaluating code snippet %d/%d." , k + 1 , len (cases ))
256+
257+ // Get the result.
258+ _ , stderr := testOutputStream (t , strings .Join (tc .Input , "\n " ))
259+
260+ // Compare the result.
261+ if len (stderr ) != len (tc .Output ) {
262+ t .Errorf ("\t %s Test case expected %d message(s) on stderr but got %d." , failure , len (tc .Output ), len (stderr ))
263+ continue
264+ }
265+ for i , expected := range tc .Output {
266+ if stderr [i ] != expected {
267+ t .Errorf ("\t %s Test case returned unexpected messages on stderr." , failure )
268+ continue cases
269+ }
270+ }
271+ t .Logf ("\t %s Returned the expected messages on stderr." , success )
272+ }
273+ }
274+
170275//==============================================================================
171276
172277// testJupyterClient holds references to the 2 sockets it uses to communicate with the kernel.
@@ -437,3 +542,31 @@ func getJSONObject(t *testing.T, jsonObjectName string, content map[string]inter
437542
438543 return value
439544}
545+
546+ // testOutputStream is a test helper that collects "stream" messages upon executing the codeIn.
547+ func testOutputStream (t * testing.T , codeIn string ) (stdout []string , stderr []string ) {
548+ t .Helper ()
549+
550+ client , closeClient := newTestJupyterClient (t )
551+ defer closeClient ()
552+
553+ _ , pub := client .executeCode (t , codeIn )
554+
555+ for _ , pubMsg := range pub {
556+ if pubMsg .Header .MsgType == "stream" {
557+ content := getMsgContentAsJSONObject (t , pubMsg )
558+ streamType := getString (t , "content" , content , "name" )
559+ streamData := getString (t , "content" , content , "text" )
560+
561+ if streamType == "stdout" {
562+ stdout = append (stdout , streamData )
563+ } else if streamType == "stderr" {
564+ stderr = append (stderr , streamData )
565+ } else {
566+ t .Fatalf ("Unknown stream type '%s'" , streamType )
567+ }
568+ }
569+ }
570+
571+ return
572+ }
0 commit comments