-
-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
resolve #107 IO module out outLn etc #120
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,95 @@ | ||||||
{- | | ||||||
Module : Iris.IO | ||||||
Copyright : (c) 2022 Dmitrii Kovanikov | ||||||
SPDX-License-Identifier : MPL-2.0 | ||||||
Maintainer : Dmitrii Kovanikov <[email protected]> | ||||||
Stability : Experimental | ||||||
Portability : Portable | ||||||
|
||||||
Functions for IO, such as writing Text to stdout and stderr. | ||||||
|
||||||
|
||||||
Usage example: | ||||||
|
||||||
@ | ||||||
import qualified Iris | ||||||
|
||||||
main = do | ||||||
Iris.outLn "This goes to stdout" | ||||||
Iris.errLn "This goes to stderr" | ||||||
@ | ||||||
|
||||||
Results in: | ||||||
|
||||||
@ | ||||||
\$ ./app | ||||||
This goes to stdout | ||||||
This goes to stderr | ||||||
@ | ||||||
|
||||||
@since x.x.x.x | ||||||
-} | ||||||
module Iris.IO ( | ||||||
out, | ||||||
outLn, | ||||||
err, | ||||||
errLn, | ||||||
) where | ||||||
|
||||||
import Data.Text (Text) | ||||||
import qualified Data.Text.IO as Text | ||||||
import System.IO (stderr, stdout) | ||||||
|
||||||
{- | Write the given Text to stdout. | ||||||
No linefeed at the end. | ||||||
|
||||||
@ | ||||||
ghci> do Iris.out "foo" >> Iris.out "bar" | ||||||
foobarghci> | ||||||
|
||||||
@ | ||||||
@since x.x.x.x | ||||||
-} | ||||||
out :: Text -> IO () | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's actually make this function work in a polymoprhic
Suggested change
|
||||||
out = Text.hPutStr stdout | ||||||
|
||||||
{- | Write the given Text to stdout with linefeed at the end. | ||||||
|
||||||
@ | ||||||
ghci> Iris.outLn "foo" >> Iris.outLn "bar" | ||||||
foo | ||||||
bar | ||||||
ghci> | ||||||
|
||||||
@ | ||||||
@since x.x.x.x | ||||||
-} | ||||||
outLn :: Text -> IO () | ||||||
outLn = Text.hPutStrLn stdout | ||||||
|
||||||
{- | Write the given Text to stderr. | ||||||
No linefeed at the end. | ||||||
|
||||||
@ | ||||||
ghci> Iris.err "foo" >> Iris.err "bar" | ||||||
foobarghci> | ||||||
|
||||||
@ | ||||||
@since x.x.x.x | ||||||
-} | ||||||
err :: Text -> IO () | ||||||
err = Text.hPutStr stderr | ||||||
|
||||||
{- | Write the given Text to stderr with linefeed at the end. | ||||||
|
||||||
@ | ||||||
ghci> Iris.errLn "foo" >> Iris.errLn "bar" | ||||||
foo | ||||||
bar | ||||||
ghci> | ||||||
|
||||||
@ | ||||||
@since x.x.x.x | ||||||
-} | ||||||
errLn :: Text -> IO () | ||||||
errLn = Text.hPutStrLn stderr |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ import System.IO (hFlush, stdout) | |
|
||
import Iris.Cli.Interactive (InteractiveMode (..)) | ||
import Iris.Env (CliEnv (..), asksCliEnv) | ||
import qualified Iris.IO as IO | ||
|
||
{- | ||
@since x.x.x.x | ||
|
@@ -107,12 +108,12 @@ yesno question defaultAnswer = do | |
where | ||
loop :: IO YesNo | ||
loop = do | ||
Text.putStr $ question <> " (yes/no) " | ||
IO.out $ question <> " (yes/no) " | ||
hFlush stdout | ||
input <- Text.getLine | ||
case parseYesNo input of | ||
Just answer -> pure answer | ||
Nothing -> do | ||
Text.putStrLn $ "I don't understand your answer: '" <> input <> "'" | ||
Text.putStrLn "Please, answer yes or no (or y, or n)" | ||
IO.out $ "I don't understand your answer: '" <> input <> "'" | ||
IO.out "Please, answer yes or no (or y, or n)" | ||
Comment on lines
+117
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be Ideally, if we could fully test this function, the tests could've caught this error. But I don't see a good way to test it. I was thinking about mocking A proper test for this function would be to write a separate executable and use some scripting-scenario framework where you can specify input and expected output. But this is a lot of work for this small test, but maybe we'll add it later eventually anyway when things stabilise.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you considered a future improvement which would be "take interactive input from file instead of stdin"? (Useful for batch installation scripts etc) If we were to support such an improvement we'd need to store the input handle in the environment. And that would also allow for unit testing this part easier. Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wrote multiple CLI tools but I never had the need to test in batch mode from a file 🤔 You don't need to mock cat my-commands | my-program For full E2E testing of CLI tools, one can use an external solution like this one: I can see how storing So I propose just changing |
||
loop |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,34 @@ | ||||||
module Test.Iris.IO (ioSpec) | ||||||
where | ||||||
|
||||||
import Test.Hspec (Spec, describe, it, shouldBe) | ||||||
|
||||||
-- Silently has side effect: writes file to tmp (or current dir) and then deletes it. | ||||||
|
||||||
import System.IO (stderr, stdout) | ||||||
import System.IO.Silently (hCapture_, hSilence) | ||||||
|
||||||
import qualified Iris.IO as IO (err, errLn, out, outLn) | ||||||
|
||||||
checkStdErr :: IO a -> IO String | ||||||
checkStdErr = hCapture_ [stderr] . hSilence [stdout] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the
Suggested change
Otherwise, the test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need to silence the handle we're not checking. otherwise erroneous behaviour will cause "TEXT" to leak into the console, messing up the test progress report. I believe the implementation is correct as it is. checkStdErr does not silence stderr. it silences stdout. i can also confirm that the tests do seem to work; hacking IO.out to print to both stdout and stderr gives ths following result:
but maybe i'm still missing something... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see now. I was confused by No need to change these helper functions 😌 |
||||||
|
||||||
checkStdOut :: IO a -> IO String | ||||||
checkStdOut = hCapture_ [stdout] . hSilence [stderr] | ||||||
|
||||||
ioSpec :: Spec | ||||||
ioSpec = | ||||||
describe "IO" $ do | ||||||
describe "out" $ do | ||||||
-- we need `flip` to get the expectation reported correctly in case of a failure | ||||||
it "writes to stdout, no LF " $ checkStdOut (IO.out "TEXT") >>= flip shouldBe "TEXT" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use shouldReturn from
Suggested change
|
||||||
it "does not write to stderr " $ checkStdErr (IO.out "TEXT") >>= flip shouldBe "" | ||||||
describe "outLn" $ do | ||||||
it "writes to stdout, LF " $ checkStdOut (IO.outLn "TEXT") >>= flip shouldBe "TEXT\n" | ||||||
it "does not write to stderr " $ checkStdErr (IO.outLn "TEXT") >>= flip shouldBe "" | ||||||
describe "err" $ do | ||||||
it "writes to sterr, no LF " $ checkStdErr (IO.err "TEXT") >>= flip shouldBe "TEXT" | ||||||
it "does not write to stdout " $ checkStdOut (IO.err "TEXT") >>= flip shouldBe "" | ||||||
describe "errLn" $ do | ||||||
it "writes to stderr, LF " $ checkStdErr (IO.errLn "TEXT") >>= flip shouldBe "TEXT\n" | ||||||
it "does not write to stdout " $ checkStdOut (IO.errLn "TEXT") >>= flip shouldBe "" |
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 guess this was copy-pasted from another module, so we can update the year 😅