From 600aeae770da9d790e2d0f882a1b1a1d4a8b7b82 Mon Sep 17 00:00:00 2001 From: "Dr. Carsten Leue" Date: Tue, 19 Sep 2023 22:31:55 +0200 Subject: [PATCH] fix: add RIOE testcases Signed-off-by: Dr. Carsten Leue --- context/readerioeither/file/file.go | 13 +- context/readerioeither/file/tempfile_test.go | 47 +++++++ context/readerioeither/file/write.go | 57 +++++++++ context/readerioeither/reader.go | 4 + context/readerioeither/reader_test.go | 122 +++++++++++++++++++ ioeither/file/close.go | 29 ----- ioeither/file/file.go | 8 ++ ioeither/file/readall.go | 2 +- ioeither/file/tempfile_test.go | 2 +- ioeither/file/write.go | 4 +- 10 files changed, 248 insertions(+), 40 deletions(-) create mode 100644 context/readerioeither/file/tempfile_test.go create mode 100644 context/readerioeither/file/write.go delete mode 100644 ioeither/file/close.go diff --git a/context/readerioeither/file/file.go b/context/readerioeither/file/file.go index a3422ad..c8eb51a 100644 --- a/context/readerioeither/file/file.go +++ b/context/readerioeither/file/file.go @@ -29,10 +29,9 @@ import ( ) var ( - openIOE = IOE.Eitherize1(os.Open) // Open opens a file for reading within the given context Open = F.Flow3( - openIOE, + IOEF.Open, RIOE.FromIOEither[*os.File], RIOE.WithContext[*os.File], ) @@ -46,11 +45,11 @@ var ( // Close closes an object func Close[C io.Closer](c C) RIOE.ReaderIOEither[any] { - return RIOE.FromIOEither(func() ET.Either[error, any] { - return ET.TryCatchError(func() (any, error) { - return c, c.Close() - }) - }) + return F.Pipe2( + c, + IOEF.Close[C], + RIOE.FromIOEither[any], + ) } // ReadFile reads a file in the scope of a context diff --git a/context/readerioeither/file/tempfile_test.go b/context/readerioeither/file/tempfile_test.go new file mode 100644 index 0000000..1795af6 --- /dev/null +++ b/context/readerioeither/file/tempfile_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package file + +import ( + "context" + "os" + "testing" + + RIOE "github.com/IBM/fp-go/context/readerioeither" + E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + "github.com/stretchr/testify/assert" +) + +func TestWithTempFile(t *testing.T) { + + res := WithTempFile(onWriteAll[*os.File]([]byte("Carsten"))) + + assert.Equal(t, E.Of[error]([]byte("Carsten")), res(context.Background())()) +} + +func TestWithTempFileOnClosedFile(t *testing.T) { + + res := WithTempFile(func(f *os.File) RIOE.ReaderIOEither[[]byte] { + return F.Pipe2( + f, + onWriteAll[*os.File]([]byte("Carsten")), + RIOE.ChainFirst(F.Constant1[[]byte](Close(f))), + ) + }) + + assert.Equal(t, E.Of[error]([]byte("Carsten")), res(context.Background())()) +} diff --git a/context/readerioeither/file/write.go b/context/readerioeither/file/write.go new file mode 100644 index 0000000..7675d54 --- /dev/null +++ b/context/readerioeither/file/write.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package file + +import ( + "context" + "io" + + RIOE "github.com/IBM/fp-go/context/readerioeither" + F "github.com/IBM/fp-go/function" +) + +func onWriteAll[W io.Writer](data []byte) func(w W) RIOE.ReaderIOEither[[]byte] { + return func(w W) RIOE.ReaderIOEither[[]byte] { + return F.Pipe1( + RIOE.TryCatch(func(ctx context.Context) func() ([]byte, error) { + return func() ([]byte, error) { + _, err := w.Write(data) + return data, err + } + }), + RIOE.WithContext[[]byte], + ) + } +} + +// WriteAll uses a generator function to create a stream, writes data to it and closes it +func WriteAll[W io.WriteCloser](data []byte) func(acquire RIOE.ReaderIOEither[W]) RIOE.ReaderIOEither[[]byte] { + onWrite := onWriteAll[W](data) + return func(onCreate RIOE.ReaderIOEither[W]) RIOE.ReaderIOEither[[]byte] { + return RIOE.WithResource[[]byte]( + onCreate, + Close[W])( + onWrite, + ) + } +} + +// Write uses a generator function to create a stream, writes data to it and closes it +func Write[R any, W io.WriteCloser](acquire RIOE.ReaderIOEither[W]) func(use func(W) RIOE.ReaderIOEither[R]) RIOE.ReaderIOEither[R] { + return RIOE.WithResource[R]( + acquire, + Close[W]) +} diff --git a/context/readerioeither/reader.go b/context/readerioeither/reader.go index 04298f2..904c623 100644 --- a/context/readerioeither/reader.go +++ b/context/readerioeither/reader.go @@ -155,6 +155,10 @@ func FromIO[A any](t IO.IO[A]) ReaderIOEither[A] { return G.FromIO[ReaderIOEither[A]](t) } +func FromLazy[A any](t L.Lazy[A]) ReaderIOEither[A] { + return G.FromIO[ReaderIOEither[A]](t) +} + // Never returns a 'ReaderIOEither' that never returns, except if its context gets canceled func Never[A any]() ReaderIOEither[A] { return G.Never[ReaderIOEither[A]]() diff --git a/context/readerioeither/reader_test.go b/context/readerioeither/reader_test.go index d5e9e3a..e795cc0 100644 --- a/context/readerioeither/reader_test.go +++ b/context/readerioeither/reader_test.go @@ -162,3 +162,125 @@ func TestRegularApply(t *testing.T) { res := applied(context.Background())() assert.Equal(t, E.Of[error]("CARSTEN"), res) } + +func TestWithResourceNoErrors(t *testing.T) { + var countAcquire, countBody, countRelease int + + acquire := FromLazy(func() int { + countAcquire++ + return countAcquire + }) + + release := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countRelease++ + return countRelease + }) + } + + body := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countBody++ + return countBody + }) + } + + resRIOE := WithResource[int](acquire, release)(body) + + res := resRIOE(context.Background())() + + assert.Equal(t, 1, countAcquire) + assert.Equal(t, 1, countBody) + assert.Equal(t, 1, countRelease) + assert.Equal(t, E.Of[error](1), res) +} + +func TestWithResourceErrorInBody(t *testing.T) { + var countAcquire, countBody, countRelease int + + acquire := FromLazy(func() int { + countAcquire++ + return countAcquire + }) + + release := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countRelease++ + return countRelease + }) + } + + err := fmt.Errorf("error in body") + body := func(int) ReaderIOEither[int] { + return Left[int](err) + } + + resRIOE := WithResource[int](acquire, release)(body) + + res := resRIOE(context.Background())() + + assert.Equal(t, 1, countAcquire) + assert.Equal(t, 0, countBody) + assert.Equal(t, 1, countRelease) + assert.Equal(t, E.Left[int](err), res) +} + +func TestWithResourceErrorInAcquire(t *testing.T) { + var countAcquire, countBody, countRelease int + + err := fmt.Errorf("error in acquire") + acquire := Left[int](err) + + release := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countRelease++ + return countRelease + }) + } + + body := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countBody++ + return countBody + }) + } + + resRIOE := WithResource[int](acquire, release)(body) + + res := resRIOE(context.Background())() + + assert.Equal(t, 0, countAcquire) + assert.Equal(t, 0, countBody) + assert.Equal(t, 0, countRelease) + assert.Equal(t, E.Left[int](err), res) +} + +func TestWithResourceErrorInRelease(t *testing.T) { + var countAcquire, countBody, countRelease int + + acquire := FromLazy(func() int { + countAcquire++ + return countAcquire + }) + + err := fmt.Errorf("error in release") + release := func(int) ReaderIOEither[int] { + return Left[int](err) + } + + body := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countBody++ + return countBody + }) + } + + resRIOE := WithResource[int](acquire, release)(body) + + res := resRIOE(context.Background())() + + assert.Equal(t, 1, countAcquire) + assert.Equal(t, 1, countBody) + assert.Equal(t, 0, countRelease) + assert.Equal(t, E.Left[int](err), res) +} diff --git a/ioeither/file/close.go b/ioeither/file/close.go deleted file mode 100644 index 8082319..0000000 --- a/ioeither/file/close.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2023 IBM Corp. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package file - -import ( - "io" - - IOE "github.com/IBM/fp-go/ioeither" -) - -// onClose closes a closeable resource -func onClose[R io.Closer](r R) IOE.IOEither[error, R] { - return IOE.TryCatchError(func() (R, error) { - return r, r.Close() - }) -} diff --git a/ioeither/file/file.go b/ioeither/file/file.go index 2c048ed..9e7ae3a 100644 --- a/ioeither/file/file.go +++ b/ioeither/file/file.go @@ -16,6 +16,7 @@ package file import ( + "io" "os" IOE "github.com/IBM/fp-go/ioeither" @@ -45,3 +46,10 @@ func Remove(name string) IOE.IOEither[error, string] { return name, os.Remove(name) }) } + +// Close closes an object +func Close[C io.Closer](c C) IOE.IOEither[error, any] { + return IOE.TryCatchError(func() (any, error) { + return c, c.Close() + }) +} diff --git a/ioeither/file/readall.go b/ioeither/file/readall.go index 0ab2368..dcc9158 100644 --- a/ioeither/file/readall.go +++ b/ioeither/file/readall.go @@ -31,7 +31,7 @@ func onReadAll[R io.Reader](r R) IOE.IOEither[error, []byte] { func ReadAll[R io.ReadCloser](acquire IOE.IOEither[error, R]) IOE.IOEither[error, []byte] { return IOE.WithResource[[]byte]( acquire, - onClose[R])( + Close[R])( onReadAll[R], ) } diff --git a/ioeither/file/tempfile_test.go b/ioeither/file/tempfile_test.go index c586a33..7da2adc 100644 --- a/ioeither/file/tempfile_test.go +++ b/ioeither/file/tempfile_test.go @@ -38,7 +38,7 @@ func TestWithTempFileOnClosedFile(t *testing.T) { return F.Pipe2( f, onWriteAll[*os.File]([]byte("Carsten")), - IOE.ChainFirst(F.Constant1[[]byte](onClose(f))), + IOE.ChainFirst(F.Constant1[[]byte](Close(f))), ) }) diff --git a/ioeither/file/write.go b/ioeither/file/write.go index 1a4bbd9..91d8213 100644 --- a/ioeither/file/write.go +++ b/ioeither/file/write.go @@ -36,7 +36,7 @@ func WriteAll[W io.WriteCloser](data []byte) func(acquire IOE.IOEither[error, W] return func(onCreate IOE.IOEither[error, W]) IOE.IOEither[error, []byte] { return IOE.WithResource[[]byte]( onCreate, - onClose[W])( + Close[W])( onWrite, ) } @@ -46,5 +46,5 @@ func WriteAll[W io.WriteCloser](data []byte) func(acquire IOE.IOEither[error, W] func Write[R any, W io.WriteCloser](acquire IOE.IOEither[error, W]) func(use func(W) IOE.IOEither[error, R]) IOE.IOEither[error, R] { return IOE.WithResource[R]( acquire, - onClose[W]) + Close[W]) }