diff --git a/config.json b/config.json
index 25db6a1a..a42ba39a 100644
--- a/config.json
+++ b/config.json
@@ -742,6 +742,15 @@
"prerequisites": [],
"difficulty": 1,
"topics": []
+ },
+ {
+ "slug": "circular-buffer",
+ "name": "Circular Buffer",
+ "uuid": "2e709c96-56e3-4a48-8948-e180b210e86d",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "topics": []
}
]
},
diff --git a/exercises/practice/circular-buffer/.docs/instructions.md b/exercises/practice/circular-buffer/.docs/instructions.md
new file mode 100644
index 00000000..2ba1fda2
--- /dev/null
+++ b/exercises/practice/circular-buffer/.docs/instructions.md
@@ -0,0 +1,58 @@
+# Instructions
+
+A circular buffer, cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end.
+
+A circular buffer first starts empty and of some predefined length.
+For example, this is a 7-element buffer:
+
+```text
+[ ][ ][ ][ ][ ][ ][ ]
+```
+
+Assume that a 1 is written into the middle of the buffer (exact starting location does not matter in a circular buffer):
+
+```text
+[ ][ ][ ][1][ ][ ][ ]
+```
+
+Then assume that two more elements are added — 2 & 3 — which get appended after the 1:
+
+```text
+[ ][ ][ ][1][2][3][ ]
+```
+
+If two elements are then removed from the buffer, the oldest values inside the buffer are removed.
+The two elements removed, in this case, are 1 & 2, leaving the buffer with just a 3:
+
+```text
+[ ][ ][ ][ ][ ][3][ ]
+```
+
+If the buffer has 7 elements then it is completely full:
+
+```text
+[5][6][7][8][9][3][4]
+```
+
+When the buffer is full an error will be raised, alerting the client that further writes are blocked until a slot becomes free.
+
+When the buffer is full, the client can opt to overwrite the oldest data with a forced write.
+In this case, two more elements — A & B — are added and they overwrite the 3 & 4:
+
+```text
+[5][6][7][8][9][A][B]
+```
+
+3 & 4 have been replaced by A & B making 5 now the oldest data in the buffer.
+Finally, if two elements are removed then what would be returned is 5 & 6 yielding the buffer:
+
+```text
+[ ][ ][7][8][9][A][B]
+```
+
+Because there is space available, if the client again uses overwrite to store C & D then the space where 5 & 6 were stored previously will be used not the location of 7 & 8.
+7 is still the oldest element and the buffer is once again full.
+
+```text
+[C][D][7][8][9][A][B]
+```
diff --git a/exercises/practice/circular-buffer/.meta/config.json b/exercises/practice/circular-buffer/.meta/config.json
new file mode 100644
index 00000000..31e13210
--- /dev/null
+++ b/exercises/practice/circular-buffer/.meta/config.json
@@ -0,0 +1,22 @@
+{
+ "authors": [
+ "m-dango"
+ ],
+ "contributors": [
+ "rcmlz"
+ ],
+ "files": {
+ "solution": [
+ "lib/CircularBuffer.rakumod"
+ ],
+ "test": [
+ "t/circular-buffer.rakutest"
+ ],
+ "example": [
+ ".meta/solutions/lib/CircularBuffer.rakumod"
+ ]
+ },
+ "blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.",
+ "source": "Wikipedia",
+ "source_url": "https://en.wikipedia.org/wiki/Circular_buffer"
+}
diff --git a/exercises/practice/circular-buffer/.meta/solutions/lib/CircularBuffer.rakumod b/exercises/practice/circular-buffer/.meta/solutions/lib/CircularBuffer.rakumod
new file mode 100644
index 00000000..9bb93662
--- /dev/null
+++ b/exercises/practice/circular-buffer/.meta/solutions/lib/CircularBuffer.rakumod
@@ -0,0 +1,74 @@
+my class X::CircularBuffer::BufferIsEmpty is Exception {
+ method message {'Buffer is empty'}
+}
+
+my class X::CircularBuffer::BufferIsFull is Exception {
+ method message {'Buffer is full'}
+}
+
+role Circular-Buffer-Interface is export {
+ has UInt $.capacity is required;
+ multi method clear( --> Bool) {...}
+ multi method read( --> Any ) {...}
+ multi method read(UInt $count --> Seq ) {...}
+ multi method write(**@items --> UInt) {...}
+ multi method overwrite(**@items --> UInt) {...}
+}
+
+class CircularBuffer does Circular-Buffer-Interface is export {
+
+ has UInt $.elems = 0;
+ has UInt $!read-pointer = 0;
+ has UInt $!write-pointer = 0;
+ has @!buffer = [];
+
+ multi method clear( --> Bool) {
+ $!elems = 0;
+ $!read-pointer = 0;
+ $!write-pointer = 0;
+ @!buffer = [];
+ True
+ }
+
+ method !is-empty( --> Bool) { $!elems == 0 }
+ method !is-full( --> Bool) { $!elems == $!capacity }
+ method !has-space-for( UInt $count, --> Bool) { $count <= ($!capacity - $!elems) }
+ method !tic( $pointer is rw, --> UInt) { $pointer = ($pointer + 1) % $!capacity }
+
+ multi method read(--> Any) { self!get-item }
+ multi method read(UInt $count --> Seq){
+ PRE { $count <= $!elems }
+ gather { take self!get-item for ^$count }
+ }
+ multi method write { 0 }
+ multi method write(**@items --> UInt){
+ PRE { +@items and self!has-space-for(+@items) or X::CircularBuffer::BufferIsFull.new.throw }
+
+ self!write-item($_) for @items;
+ +@items
+ }
+ multi method overwrite(**@items --> UInt){
+ PRE { +@items }
+
+ for @items -> $item {
+ self!tic($!read-pointer) if self!is-full;
+ self!write-item( $item )
+ }
+ +@items
+ }
+ method !get-item {
+ PRE { not self!is-empty or X::CircularBuffer::BufferIsEmpty.new.throw }
+
+ LEAVE { $!elems-- unless self!is-empty;
+ @!buffer[ $!read-pointer ] = Nil;
+ self!tic($!read-pointer)
+ }
+ @!buffer[ $!read-pointer ]
+ }
+ method !write-item($item) {
+ LEAVE { $!elems++ unless self!is-full;
+ self!tic($!write-pointer)
+ }
+ @!buffer[ $!write-pointer ] = $item.clone # clone: slow but prevents reference capturing/leaking!
+ }
+}
diff --git a/exercises/practice/circular-buffer/.meta/solutions/t/circular-buffer.rakutest b/exercises/practice/circular-buffer/.meta/solutions/t/circular-buffer.rakutest
new file mode 120000
index 00000000..c70be607
--- /dev/null
+++ b/exercises/practice/circular-buffer/.meta/solutions/t/circular-buffer.rakutest
@@ -0,0 +1 @@
+../../../t/circular-buffer.rakutest
\ No newline at end of file
diff --git a/exercises/practice/circular-buffer/.meta/template-data.yaml b/exercises/practice/circular-buffer/.meta/template-data.yaml
new file mode 100644
index 00000000..71976419
--- /dev/null
+++ b/exercises/practice/circular-buffer/.meta/template-data.yaml
@@ -0,0 +1,173 @@
+properties:
+ run:
+ test: |-
+ my $ops = %case.map(-> %op {
+ given %op {
+ when 'read' {
+ if %op {
+ sprintf(q:to/CASE/, %op).trim-trailing;
+ cmp-ok(
+ $buffer.read,
+ "==",
+ %s,
+ "read buffer",
+ );
+ CASE
+ }
+ else {
+ sprintf(q:to/CASE/).trim-trailing;
+ throws-like(
+ { $buffer.read },
+ X::CircularBuffer::BufferIsEmpty,
+ "read error",
+ );
+ CASE
+ }
+ }
+ when 'write' {
+ if %op {
+ sprintf(q:to/CASE/, %op- ).trim-trailing;
+ lives-ok(
+ { $buffer.write(%s) },
+ "write buffer",
+ );
+ CASE
+ }
+ else {
+ sprintf(q:to/CASE/, %op
- ).trim-trailing;
+ throws-like(
+ { $buffer.write(%s) },
+ X::CircularBuffer::BufferIsFull,
+ "write error",
+ );
+ CASE
+ }
+ }
+ when 'clear' {
+ sprintf(q:to/CASE/).trim-trailing;
+ lives-ok(
+ { $buffer.clear },
+ "clear buffer",
+ );
+ CASE
+ }
+ when 'overwrite' {
+ sprintf(q:to/CASE/, %op
- ).trim-trailing;
+ lives-ok(
+ { $buffer.overwrite(%s) },
+ "overwrite buffer",
+ );
+ CASE
+ }
+ default {
+ ' flunk "NYI";'
+ }
+ }
+ }).join("\n\n");
+
+ sprintf(q:to/END/, %case.raku, %case, $ops);
+ subtest %s => {
+ my $buffer := CircularBuffer.new( :capacity(%s) );
+
+ %s
+ };
+ END
+
+unit: false
+
+example: |-
+ my class X::CircularBuffer::BufferIsEmpty is Exception {
+ method message {'Buffer is empty'}
+ }
+
+ my class X::CircularBuffer::BufferIsFull is Exception {
+ method message {'Buffer is full'}
+ }
+
+ role Circular-Buffer-Interface is export {
+ has UInt $.capacity is required;
+ multi method clear( --> Bool) {...}
+ multi method read( --> Any ) {...}
+ multi method read(UInt $count --> Seq ) {...}
+ multi method write(**@items --> UInt) {...}
+ multi method overwrite(**@items --> UInt) {...}
+ }
+
+ class CircularBuffer does Circular-Buffer-Interface is export {
+
+ has UInt $.elems = 0;
+ has UInt $!read-pointer = 0;
+ has UInt $!write-pointer = 0;
+ has @!buffer = [];
+
+ multi method clear( --> Bool) {
+ $!elems = 0;
+ $!read-pointer = 0;
+ $!write-pointer = 0;
+ @!buffer = [];
+ True
+ }
+
+ method !is-empty( --> Bool) { $!elems == 0 }
+ method !is-full( --> Bool) { $!elems == $!capacity }
+ method !has-space-for( UInt $count, --> Bool) { $count <= ($!capacity - $!elems) }
+ method !tic( $pointer is rw, --> UInt) { $pointer = ($pointer + 1) % $!capacity }
+
+ multi method read(--> Any) { self!get-item }
+ multi method read(UInt $count --> Seq){
+ PRE { $count <= $!elems }
+ gather { take self!get-item for ^$count }
+ }
+ multi method write { 0 }
+ multi method write(**@items --> UInt){
+ PRE { +@items and self!has-space-for(+@items) or X::CircularBuffer::BufferIsFull.new.throw }
+
+ self!write-item($_) for @items;
+ +@items
+ }
+ multi method overwrite(**@items --> UInt){
+ PRE { +@items }
+
+ for @items -> $item {
+ self!tic($!read-pointer) if self!is-full;
+ self!write-item( $item )
+ }
+ +@items
+ }
+ method !get-item {
+ PRE { not self!is-empty or X::CircularBuffer::BufferIsEmpty.new.throw }
+
+ LEAVE { $!elems-- unless self!is-empty;
+ @!buffer[ $!read-pointer ] = Nil;
+ self!tic($!read-pointer)
+ }
+ @!buffer[ $!read-pointer ]
+ }
+ method !write-item($item) {
+ LEAVE { $!elems++ unless self!is-full;
+ self!tic($!write-pointer)
+ }
+ @!buffer[ $!write-pointer ] = $item.clone # clone: slow but prevents reference capturing/leaking!
+ }
+ }
+
+stub: |-
+ my class X::CircularBuffer::BufferIsEmpty is Exception {
+ method message {'Buffer is empty'}
+ }
+
+ my class X::CircularBuffer::BufferIsFull is Exception {
+ method message {'Buffer is full'}
+ }
+
+ class CircularBuffer {
+ has $.capacity;
+
+ method read () {}
+
+ method write ($item) {}
+
+ method clear () {}
+
+ method overwrite ($item) {}
+ }
diff --git a/exercises/practice/circular-buffer/.meta/tests.toml b/exercises/practice/circular-buffer/.meta/tests.toml
new file mode 100644
index 00000000..0fb3143d
--- /dev/null
+++ b/exercises/practice/circular-buffer/.meta/tests.toml
@@ -0,0 +1,52 @@
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[28268ed4-4ff3-45f3-820e-895b44d53dfa]
+description = "reading empty buffer should fail"
+
+[2e6db04a-58a1-425d-ade8-ac30b5f318f3]
+description = "can read an item just written"
+
+[90741fe8-a448-45ce-be2b-de009a24c144]
+description = "each item may only be read once"
+
+[be0e62d5-da9c-47a8-b037-5db21827baa7]
+description = "items are read in the order they are written"
+
+[2af22046-3e44-4235-bfe6-05ba60439d38]
+description = "full buffer can't be written to"
+
+[547d192c-bbf0-4369-b8fa-fc37e71f2393]
+description = "a read frees up capacity for another write"
+
+[04a56659-3a81-4113-816b-6ecb659b4471]
+description = "read position is maintained even across multiple writes"
+
+[60c3a19a-81a7-43d7-bb0a-f07242b1111f]
+description = "items cleared out of buffer can't be read"
+
+[45f3ae89-3470-49f3-b50e-362e4b330a59]
+description = "clear frees up capacity for another write"
+
+[e1ac5170-a026-4725-bfbe-0cf332eddecd]
+description = "clear does nothing on empty buffer"
+
+[9c2d4f26-3ec7-453f-a895-7e7ff8ae7b5b]
+description = "overwrite acts like write on non-full buffer"
+
+[880f916b-5039-475c-bd5c-83463c36a147]
+description = "overwrite replaces the oldest item on full buffer"
+
+[bfecab5b-aca1-4fab-a2b0-cd4af2b053c3]
+description = "overwrite replaces the oldest item remaining in buffer following a read"
+
+[9cebe63a-c405-437b-8b62-e3fdc1ecec5a]
+description = "initial clear does not affect wrapping around"
diff --git a/exercises/practice/circular-buffer/lib/CircularBuffer.rakumod b/exercises/practice/circular-buffer/lib/CircularBuffer.rakumod
new file mode 100644
index 00000000..6d2a635c
--- /dev/null
+++ b/exercises/practice/circular-buffer/lib/CircularBuffer.rakumod
@@ -0,0 +1,19 @@
+my class X::CircularBuffer::BufferIsEmpty is Exception {
+ method message {'Buffer is empty'}
+}
+
+my class X::CircularBuffer::BufferIsFull is Exception {
+ method message {'Buffer is full'}
+}
+
+class CircularBuffer {
+ has $.capacity;
+
+ method read () {}
+
+ method write ($item) {}
+
+ method clear () {}
+
+ method overwrite ($item) {}
+}
diff --git a/exercises/practice/circular-buffer/t/circular-buffer.rakutest b/exercises/practice/circular-buffer/t/circular-buffer.rakutest
new file mode 100755
index 00000000..a9377e36
--- /dev/null
+++ b/exercises/practice/circular-buffer/t/circular-buffer.rakutest
@@ -0,0 +1,399 @@
+#!/usr/bin/env raku
+use Test;
+use lib $?FILE.IO.parent(2).add('lib');
+use CircularBuffer;
+
+subtest "reading empty buffer should fail" => { # begin: 28268ed4-4ff3-45f3-820e-895b44d53dfa
+ my $buffer := CircularBuffer.new( :capacity(1) );
+
+ throws-like(
+ { $buffer.read },
+ X::CircularBuffer::BufferIsEmpty,
+ "read error",
+ );
+}; # end: 28268ed4-4ff3-45f3-820e-895b44d53dfa
+
+subtest "can read an item just written" => { # begin: 2e6db04a-58a1-425d-ade8-ac30b5f318f3
+ my $buffer := CircularBuffer.new( :capacity(1) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 1,
+ "read buffer",
+ );
+}; # end: 2e6db04a-58a1-425d-ade8-ac30b5f318f3
+
+subtest "each item may only be read once" => { # begin: 90741fe8-a448-45ce-be2b-de009a24c144
+ my $buffer := CircularBuffer.new( :capacity(1) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 1,
+ "read buffer",
+ );
+
+ throws-like(
+ { $buffer.read },
+ X::CircularBuffer::BufferIsEmpty,
+ "read error",
+ );
+}; # end: 90741fe8-a448-45ce-be2b-de009a24c144
+
+subtest "items are read in the order they are written" => { # begin: be0e62d5-da9c-47a8-b037-5db21827baa7
+ my $buffer := CircularBuffer.new( :capacity(2) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(2) },
+ "write buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 1,
+ "read buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 2,
+ "read buffer",
+ );
+}; # end: be0e62d5-da9c-47a8-b037-5db21827baa7
+
+subtest "full buffer can't be written to" => { # begin: 2af22046-3e44-4235-bfe6-05ba60439d38
+ my $buffer := CircularBuffer.new( :capacity(1) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ throws-like(
+ { $buffer.write(2) },
+ X::CircularBuffer::BufferIsFull,
+ "write error",
+ );
+}; # end: 2af22046-3e44-4235-bfe6-05ba60439d38
+
+subtest "a read frees up capacity for another write" => { # begin: 547d192c-bbf0-4369-b8fa-fc37e71f2393
+ my $buffer := CircularBuffer.new( :capacity(1) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 1,
+ "read buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(2) },
+ "write buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 2,
+ "read buffer",
+ );
+}; # end: 547d192c-bbf0-4369-b8fa-fc37e71f2393
+
+subtest "read position is maintained even across multiple writes" => { # begin: 04a56659-3a81-4113-816b-6ecb659b4471
+ my $buffer := CircularBuffer.new( :capacity(3) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(2) },
+ "write buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 1,
+ "read buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(3) },
+ "write buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 2,
+ "read buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 3,
+ "read buffer",
+ );
+}; # end: 04a56659-3a81-4113-816b-6ecb659b4471
+
+subtest "items cleared out of buffer can't be read" => { # begin: 60c3a19a-81a7-43d7-bb0a-f07242b1111f
+ my $buffer := CircularBuffer.new( :capacity(1) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.clear },
+ "clear buffer",
+ );
+
+ throws-like(
+ { $buffer.read },
+ X::CircularBuffer::BufferIsEmpty,
+ "read error",
+ );
+}; # end: 60c3a19a-81a7-43d7-bb0a-f07242b1111f
+
+subtest "clear frees up capacity for another write" => { # begin: 45f3ae89-3470-49f3-b50e-362e4b330a59
+ my $buffer := CircularBuffer.new( :capacity(1) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.clear },
+ "clear buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(2) },
+ "write buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 2,
+ "read buffer",
+ );
+}; # end: 45f3ae89-3470-49f3-b50e-362e4b330a59
+
+subtest "clear does nothing on empty buffer" => { # begin: e1ac5170-a026-4725-bfbe-0cf332eddecd
+ my $buffer := CircularBuffer.new( :capacity(1) );
+
+ lives-ok(
+ { $buffer.clear },
+ "clear buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 1,
+ "read buffer",
+ );
+}; # end: e1ac5170-a026-4725-bfbe-0cf332eddecd
+
+subtest "overwrite acts like write on non-full buffer" => { # begin: 9c2d4f26-3ec7-453f-a895-7e7ff8ae7b5b
+ my $buffer := CircularBuffer.new( :capacity(2) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.overwrite(2) },
+ "overwrite buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 1,
+ "read buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 2,
+ "read buffer",
+ );
+}; # end: 9c2d4f26-3ec7-453f-a895-7e7ff8ae7b5b
+
+subtest "overwrite replaces the oldest item on full buffer" => { # begin: 880f916b-5039-475c-bd5c-83463c36a147
+ my $buffer := CircularBuffer.new( :capacity(2) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(2) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.overwrite(3) },
+ "overwrite buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 2,
+ "read buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 3,
+ "read buffer",
+ );
+}; # end: 880f916b-5039-475c-bd5c-83463c36a147
+
+subtest "overwrite replaces the oldest item remaining in buffer following a read" => { # begin: bfecab5b-aca1-4fab-a2b0-cd4af2b053c3
+ my $buffer := CircularBuffer.new( :capacity(3) );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(2) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(3) },
+ "write buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 1,
+ "read buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(4) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.overwrite(5) },
+ "overwrite buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 3,
+ "read buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 4,
+ "read buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 5,
+ "read buffer",
+ );
+}; # end: bfecab5b-aca1-4fab-a2b0-cd4af2b053c3
+
+subtest "initial clear does not affect wrapping around" => { # begin: 9cebe63a-c405-437b-8b62-e3fdc1ecec5a
+ my $buffer := CircularBuffer.new( :capacity(2) );
+
+ lives-ok(
+ { $buffer.clear },
+ "clear buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(1) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.write(2) },
+ "write buffer",
+ );
+
+ lives-ok(
+ { $buffer.overwrite(3) },
+ "overwrite buffer",
+ );
+
+ lives-ok(
+ { $buffer.overwrite(4) },
+ "overwrite buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 3,
+ "read buffer",
+ );
+
+ cmp-ok(
+ $buffer.read,
+ "==",
+ 4,
+ "read buffer",
+ );
+
+ throws-like(
+ { $buffer.read },
+ X::CircularBuffer::BufferIsEmpty,
+ "read error",
+ );
+}; # end: 9cebe63a-c405-437b-8b62-e3fdc1ecec5a
+
+done-testing;