i spent some time with PDD22 this week. i've started writing tests for
the ParrotIO object, which you'll find in the attached patch. you'll
also find some notes i've made both in the test file, and in the spec.
i find writing spec-based tests to be extremely enlightening. the act
of writing code (tests) based on the spec helps solidify the use
cases, since they make themselves clear as you're coding. also, using
a testing mentality, which promotes thoughts about failure modes and
edge cases, helps find places where the spec may be unclear or missing
information. lastly, having a rather complete set of tests for a
specification will make it's implementation worlds easier and faster.
i'd like to encourage this style among interested parroters. other
specs (namespaces, for one--but really any PDD, approved or draft)
could use this treatment as well. i plan to continue to develop tests
this way, and i hope some of you will join me. as always, comments and
questions are welcome and appreciated.
~jerry
i'll apply this patch in two days if nobody objects. in the meantime,
i'll be working on tests for the remainder of the spec (eg. status
object pmc api, and i/o stream, filesystem, and network opcodes.)
------------------------------------------------------------------------
Index: docs/pdds/pdd22_io.pod
===================================================================
--- docs/pdds/pdd22_io.pod (revision 16821)
+++ docs/pdds/pdd22_io.pod (working copy)
@@ -104,6 +104,13 @@
=over 4
+{{ the io object should be given an official name and namespace, eg. ParrotIO
+ or [ 'ParrotIO' ], and references throughout the doc should consistently
+ use this name, instead of generic terms like 'an I/O stream object'.
+ I used ParrotIO cause that's what it's currently called, but Stream might
+ be a more appropriate name.
+}}
+
=item new
$P0 = new ParrotIO
@@ -111,6 +118,8 @@
Creates a new I/O stream object. [Note that this is usually performed
via the C<open> opcode.]
+{{ how does .open usually perform .new? .open requires a ParrotIO object }}
+
=item open
$P0.open()
@@ -123,6 +132,8 @@
etc), using the same format as the C<open> opcode: 'r' for read, 'w' for
write, 'a' for append, and 'p' for pipe.
+{{ no async form for .open? will it create a file if none exists? }}
+
=item close
$P0.close()
@@ -136,6 +147,8 @@
over just leaving the object for the GC to clean-up, but it does give
you the option of executing an action when the stream has been closed.]
+{{ no async for .close? }}
+
=item print
$P0.print($I1)
@@ -153,6 +166,8 @@
argument $P2. When the print operation is complete, it invokes the callback,
passing it a status object.
+{{ how about a .say method that appends a record separator automatically? }}
+
=item read
$S0 = $P1.read($I2)
@@ -173,6 +188,9 @@
as the information about the character encoding of the return value is
contained in the string.]
+{{ if you specify more bytes than are available, does it let you know? }}
+{{ how about a .chomp method? }}
+
=item readline
$S0 = $P1.readline()
@@ -188,6 +206,8 @@
is complete, it invokes the callback, passing it a status object and a
string of bytes.
+{{ does this autochomp? }}
+
=item record_separator
$S0 = $P1.record_separator()
@@ -195,6 +215,8 @@
Accessor (get and set) for the I/O stream's record separator attribute.
+{{ what's the default? --perhaps dependent upon stream type? }}
+
=item buffer_type
$I0 = $P1.buffer_type()
@@ -230,6 +252,12 @@
provide the logic that marks the buffer as "full" when it can't hold the
next codepoint even if there are empty bytes in the buffer.
+{{
+ are only 'postive integer values' allowed?
+ what happens when changing size on a buffer with existing data?
+ does this only work with PIO_FULLBUF mode?
+}}
+
=item get_fd
$I0 = $P1.get_fd()
@@ -242,6 +270,8 @@
No asynchronous version.
+{{ NOTE: use a config probe (behind does or can) to determine support }}
+
=back
=head2 Status Object PMC API
Index: t/pmc/parrotio.t
===================================================================
--- t/pmc/parrotio.t (revision 16821)
+++ t/pmc/parrotio.t (working copy)
@@ -6,7 +6,7 @@
use warnings;
use lib qw( . lib ../lib ../../lib );
use Test::More;
-use Parrot::Test tests => 1;
+use Parrot::Test tests => 6;
=head1 NAME
@@ -22,15 +22,270 @@
=cut
+# L<PDD22/I\/O PMC API/item new>
pir_output_is( <<'CODE', <<'OUT', 'new' );
.sub 'test' :main
new P0, .ParrotIO
- print "ok 1\n"
+ say "ok 1 - $P0 = new .ParrotIO"
.end
CODE
-ok 1
+ok 1 - $P0 = new .ParrotIO
OUT
+
+# L<PDD22/I\/O PMC API/item open.*item close>
+pir_output_is( <<'CODE', <<'OUT', 'open and close - synchronous', todo => 'not
yet implemented' );
+.sub 'test' :main
+ $P0 = new .ParrotIO
+ $P0.open('README')
+ say "ok 1 - $P0.open($S1)"
+
+ $P0.close()
+ say "ok 2 - $P0.close()"
+
+ $P0.open('README', 'rw')
+ say "ok 3 - $P0.open($S1, $S2)"
+
+ $P0.close()
+ $P0.open()
+ say "ok 4 - $P0.open()"
+
+ push_eh eh_bad_file_1
+ $P0.open('bad_file')
+ clear_eh
+
+ test_5:
+ push_eh eh_bad_file_2
+ $P0.open('bad_file', 'r')
+ clear_eh
+
+ test_6:
+# $P0.open('new_file', 'w') # TODO undefined behavior
+
+ goto end
+
+ bad_file_1:
+ say "ok 5 - $P0.open($S1) # with bad file"
+ goto test_5
+ bad_file_2:
+ say "ok 6 - $P0.open($S1, $S2) # with bad file"
+ goto test_6
+ end:
+.end
+CODE
+ok 1 - $P0.open($S1)
+ok 2 - $P0.close()
+ok 3 - $P0.open($S1, $S2)
+ok 4 - $P0.open()
+ok 5 - $P0.open($S1) # with bad file
+ok 6 - $P0.open($S1, $S2) # with bad file
+OUT
+
+
+SKIP: {
+ skip 'no asynch calls yet' => 1;
+
+pir_output_is( <<'CODE', <<'OUT', 'open and close - asynchronous' );
+.sub 'test' :main
+ $P1 = # TODO create a callback here
+ $P0 = new .ParrotIO
+
+ $P0.open('README')
+ say "ok 1 - $P0.open($S1)"
+
+ $P0.close()
+ say "ok 2 - $P0.close($P1)"
+
+ $P0.open('README', 'rw')
+ say "ok 3 - $P0.open($S1, $S2)"
+
+ $P0.close()
+ $P0.open()
+ say "ok 4 - $P0.open()"
+
+ cleanup:
+ $P0.close()
+.end
+CODE
+ok 1 - $P0.open($S1)
+ok 2 - $P0.close()
+ok 3 - $P0.open($S1, $S2)
+ok 4 - $P0.open()
+OUT
+}
+
+
+# L<PDD22/I\/O PMC API/item print.*item readline>
+pir_output_is( <<'CODE', <<'OUT', 'print, read, and readline - synchronous', todo
=> 'not yet implemented' );
+.sub 'test' :main
+ $P0 = new .ParrotIO
+ $P0.open('README')
+
+ $S0 = $P0.read(14) # bytes
+ if $S0 == 'This is Parrot' goto ok_1
+ print 'not '
+ ok_1:
+ say "ok 1 - $S0 = $P1.read($I2)"
+
+ $S0 = $P0.read(9) # bytes
+ if $S0 == ', version' goto ok_2
+ print 'not '
+ ok_2:
+ say "ok 2 - $S0 = $P1.read($I2) # again on same stream'
+
+ $P0.print(123)
+ $P0.print(456.789)
+ $P0.print("squawk\n")
+ $P1 = new .Integer
+ $P1 = 42
+ $P0.print($P1)
+ say "ok 3 - $P0.print(${I,N,S,P}1)"
+
+ $S0 = $P0.readline()
+ # XXX: chomp $S0
+ if $S0 == '123456.789000squawk' goto ok_4
+ print 'not '
+ ok_4:
+ say "ok 4 - $S0 = $P1.readline($I2)"
+
+ $S0 = $P0.readline()
+ # XXX: chomp $S0
+ if $S0 == '42' goto ok_5
+ print 'not '
+ ok_5:
+ say "ok 5 - $S0 = $P1.readline($I2) # again on same stream"
+
+.end
+CODE
+ok 1 - $S0 = $P1.read($I2)
+ok 2 - $S0 = $P1.read($I2) # again on same stream
+ok 3 - $P0.print(${I,N,S,P}1)
+ok 4 - $S0 = $P1.readline($I2)
+ok 5 - $S0 = $P1.readline($I2) # again on same stream
+OUT
+
+
+# TODO test reading/writing code points once supported
+
+
+# TODO test reading long chunks, eof, and across newlines
+
+
+# TODO pir_output_is( <<'CODE', <<'OUT', 'print, read, and readline -
asynchronous', todo => 'not yet implemented' );
+
+
+# L<PDD22/I\/O PMC API/item record_separator>
+pir_output_is( <<'CODE', <<'OUT', 'record_separator', todo => 'not yet
implemented' );
+.sub 'test' :main
+ $P0 = new .ParrotIO
+
+ $S0 = $P0.record_separator()
+ # XXX: what's expected default?
+ if $S0 == expected_default goto ok_1
+ print 'not '
+ ok_1:
+ say "ok 1 - $S0 = $P1.record_separator() # default"
+
+ $S99 = 'abc'
+ $P0.record_separator($S99)
+ $S0 = $P0.record_separator()
+ if $S0 == $S99 goto ok_2
+ print 'not '
+ ok_2:
+ say "ok 2 - $P0.record_separator($S1)"
+
+ # XXX: convert to .say (if it's approved)
+ $P0.print(123)
+ $S0 = $P0.record_separator()
+ $P0.print($S0)
+ $P0.print(456)
+
+ $S0 = $P0.readline()
+ if $S0 == '123abc' goto ok_3 # assumes no autochomp
+ print 'not '
+ ok_3:
+ say 'ok 3 - $P0.record_separator() # .readline works as expected'
+.end
+CODE
+ok 1 - $S0 = $P1.record_separator() # default
+ok 2 - $P0.record_separator($S1)
+ok 3 - $P0.record_separator() # .readline works as expected
+OUT
+
+
+# L<PDD22/I\/O PMC API/item buffer_type>
+pir_output_is( <<'CODE', <<'OUT', 'buffer_type', todo => 'not yet implemented'
);
+.sub 'test' :main
+ .include 'ParrotIO.pasm' # TODO find out what it's called
+ $P0 = new .ParrotIO
+
+ $P0.buffer_type('unbuffered')
+ $I0 = $P0.buffer_type()
+ if $I0 == 0 goto ok_1 # TODO replace with PIO_NONBUF
+ print 'not '
+ ok_1:
+ say "ok 1 - $I0 = $P1.buffer_type() # PIO_NONBUF"
+
+ $P0.buffer_type(0) # TODO replace with PIO_NONBUF
+ $S0 = $P0.buffer_type()
+ if $S0 == 'unbuffered' goto ok_2
+ print 'not '
+ ok_2:
+ say "ok 2 - $S0 = $P1.buffer_type() # PIO_NONBUF"
+
+ $P0.buffer_type('line-buffered')
+ $I0 = $P0.buffer_type()
+ if $I0 == 1 goto ok_3 # TODO replace with PIO_LINEBUF
+ print 'not '
+ ok_3:
+ say "ok 3 - $I0 = $P1.buffer_type() # PIO_LINEBUF"
+
+ $P0.buffer_type(1) # TODO replace with PIO_LINEBUF
+ $S0 = $P0.buffer_type()
+ if $S0 == 'line-buffered' goto ok_4
+ print 'not '
+ ok_4:
+ say "ok 4 - $S0 = $P1.buffer_type() # PIO_LINEBUF"
+
+ $P0.buffer_type('full-buffered')
+ $I0 = $P0.buffer_type()
+ if $I0 == 2 goto ok_5 # TODO replace with PIO_FULLBUF
+ print 'not '
+ ok_5:
+ say "ok 5 - $I0 = $P1.buffer_type() # PIO_FULLBUF"
+
+ $P0.buffer_type(2) # TODO replace with PIO_FULLBUF
+ $S0 = $P0.buffer_type()
+ if $S0 == 'full-buffered' goto ok_6
+ print 'not '
+ ok_6:
+ say "ok 6 - $S0 = $P1.buffer_type() # PIO_FULLBUF"
+.end
+CODE
+ok 1 - $I0 = $P1.buffer_type() # PIO_NONBUF"
+ok 2 - $S0 = $P1.buffer_type() # PIO_NONBUF"
+ok 3 - $I0 = $P1.buffer_type() # PIO_LINEBUF"
+ok 4 - $S0 = $P1.buffer_type() # PIO_LINEBUF"
+ok 5 - $I0 = $P1.buffer_type() # PIO_FULLBUF"
+ok 6 - $S0 = $P1.buffer_type() # PIO_FULLBUF"
+OUT
+
+
+# TODO test effects of buffer_type, not just set/get
+
+
+# TODO
+# L<PDD22/I\/O PMC API/item buffer_size>
+# NOTES: try setting positive, zero, negative int
+# perform print and read ops
+# change buffer size while it contains data
+# try with all 'buffer_type' modes
+
+
+# L<PDD22/I\/O PMC API/item get_fd>
+# NOTES: this is going to be platform dependent
+
+
# Local Variables:
# mode: cperl
# cperl-indent-level: 4