From a04825a5ceb062483af9dd1f4befc8e766a7b075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Mon, 13 Jun 2022 11:11:46 +0200 Subject: [PATCH] Add `Text.write` Function (#3518) Implements https://www.pivotaltracker.com/story/show/182309026 --- CHANGELOG.md | 2 + .../lib/Standard/Base/0.0.0-dev/package.yaml | 2 +- .../Base/0.0.0-dev/src/Error/Common.enso | 2 +- .../Base/0.0.0-dev/src/System/File.enso | 117 ++++++++---------- .../System/File/Existing_File_Behavior.enso | 89 +++++++++++++ .../Table/0.0.0-dev/src/Data/Table.enso | 2 +- .../lib/Standard/Test/0.0.0-dev/src/Main.enso | 2 +- .../interpreter/runtime/data/EnsoFile.java | 5 + test/Table_Tests/data/transient/.gitignore | 2 +- test/Table_Tests/src/Delimited_Read_Spec.enso | 4 +- test/Tests/data/transient/.gitignore | 2 +- test/Tests/src/System/File_Spec.enso | 105 +++++++++++++++- .../System/Reporting_Stream_Decoder_Spec.enso | 6 +- 13 files changed, 256 insertions(+), 84 deletions(-) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/System/File/Existing_File_Behavior.enso diff --git a/CHANGELOG.md b/CHANGELOG.md index 01271438bc7..65709ded6cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -138,6 +138,7 @@ - [Renamed `File_Format.Text` to `Plain_Text`, updated `File_Format.Delimited` API and added builders for customizing less common settings.][3516] - [Allow control of sort direction in `First` and `Last` aggregations.][3517] +- [Implemented `Text.write`, replacing `File.write_text`.][3518] [debug-shortcuts]: https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug @@ -217,6 +218,7 @@ [3515]: https://github.com/enso-org/enso/pull/3515 [3516]: https://github.com/enso-org/enso/pull/3516 [3517]: https://github.com/enso-org/enso/pull/3517 +[3518]: https://github.com/enso-org/enso/pull/3518 #### Enso Compiler diff --git a/distribution/lib/Standard/Base/0.0.0-dev/package.yaml b/distribution/lib/Standard/Base/0.0.0-dev/package.yaml index 2023e93b36e..a079cea2361 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/package.yaml +++ b/distribution/lib/Standard/Base/0.0.0-dev/package.yaml @@ -53,4 +53,4 @@ component-groups: - Standard.Base.Data.Vector.Vector.distinct - Output: exports: - - Standard.Base.System.File.File.write_text + - Standard.Base.Data.Text.Text.write diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Error/Common.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Error/Common.enso index 1ba22dd37c8..feb16a59a62 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Error/Common.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Error/Common.enso @@ -576,4 +576,4 @@ Unimplemented_Error.to_display_text = "An implementation is missing: " + this.me example_unimplemented = Errors.unimplemented unimplemented : Text -> Void -unimplemented message="" = Panic.throw (Unimplemented_Error message) \ No newline at end of file +unimplemented message="" = Panic.throw (Unimplemented_Error message) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso index f92dd21f3f2..84d5d366f73 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso @@ -1,6 +1,7 @@ from Standard.Base import all import Standard.Base.System.File.Option +import Standard.Base.System.File.Existing_File_Behavior import Standard.Base.Data.Text.Matching_Mode import Standard.Base.Data.Text.Text_Sub_Range from Standard.Base.Data.Text.Encoding as Encoding_Module import Encoding @@ -13,8 +14,10 @@ polyglot java import java.io.InputStream as Java_Input_Stream polyglot java import java.io.IOException polyglot java import java.nio.file.AccessDeniedException polyglot java import java.nio.file.NoSuchFileException +polyglot java import java.nio.file.FileAlreadyExistsException polyglot java import java.nio.file.FileSystems polyglot java import java.nio.file.Path +polyglot java import java.nio.file.StandardCopyOption ## ALIAS New File @@ -89,36 +92,6 @@ read_text path (encoding=Encoding.utf_8) (on_problems=Report_Warning) = _ -> Error.throw (Illegal_Argument_Error "path should be either a File or a Text") file.read_text encoding on_problems -## ALIAS Write Text File - - Open and write to the file at the provided `path`. - - Arguments: - - path: The path of the file to open and read the contents of. It will - accept a textual path or a file. - - contents: The text to write to the file. - - encoding: The text encoding to decode the file with. Defaults to UTF-8. - - ? Module or Instance? - If you have a variable `file` of type `File`, we recommend calling the - `.read_text` method on it directly, rather than using - `File.read_text file`. The later, however, will still work. - - > Example - Read the `data.csv` file from the Examples project. - - import Standard.Base.System.File - import Standard.Examples - - example_read = File.read_text Examples.csv_path -write_text : (Text | File) -> Text -> Encoding -> Text -write_text path contents (encoding=Encoding.utf_8) = - file = case path of - Text -> (here.new path) - File _ -> path - _ -> Error.throw (Illegal_Argument_Error "path should be either a File or a Text") - file.write_text contents encoding - ## ALIAS Current Directory Returns the current working directory (CWD) of the current program. @@ -311,20 +284,6 @@ type File opts = [Option.Append, Option.Create] this.with_output_stream opts (_.write_bytes contents) - ## Appends a UTF-8 encoded `Text` at the end of this file. - - Arguments: - - contents: The UTF-8 encoded text to append to the file. - - > Example - Append the text "hello" to a file. - - import Standard.Examples - - example_append = Examples.scratch_file.append "hello" - append : Text -> Nothing ! File_Error - append contents = this.append_bytes contents.utf_8 - ## Writes a number of bytes into this file, replacing any existing contents. Arguments: @@ -344,27 +303,6 @@ type File this.with_output_stream opts (_.write_bytes contents) Nothing - ## ALIAS Write Text File - - Writes a `Text` into this file with specified encoding, replacing any - existing contents. - - Arguments: - - contents: The text to write to the file. - - encoding: The text encoding to decode the file with. Defaults to UTF-8. - - If the file does not exist, it will be created. - - > Example - Write the text "hello" to a file. - - import Standard.Examples - - example_write = Examples.scratch_file.write "hello" - write_text : Text -> Encoding -> Nothing ! File_Error - write_text contents (encoding=Encoding.utf_8) = - this.write_bytes <| contents.bytes encoding - ## Join two path segments together. Arguments: @@ -561,12 +499,24 @@ type File example_delete = file = Examples.data_dir / "my_file" - file.write_text "hello" + "hello".write file file.delete delete : Nothing ! File_Error delete = here.handle_java_exceptions this this.prim_file.delete + ## Moves the file to the specified destination. + + Arguments: + - destination: the destination to move the file to. + - replace_existing: specifies if the operation should proceed if the + destination file already exists. Defaults to `True`. + move_to : File -> Boolean -> Nothing ! File_Error + move_to destination replace_existing=True = + here.handle_java_exceptions this <| case replace_existing of + True -> this.prim_file.move destination.prim_file StandardCopyOption.REPLACE_EXISTING + False -> this.prim_file.move destination.prim_file + ## Deletes the file if it exists on disk. If the file is a directory, it must be empty, otherwise a `Panic` will @@ -891,8 +841,9 @@ handle_java_exceptions file ~action = Converts a Java `IOException` into its Enso counterpart. wrap_io_exception file io_exception = if Java.is_instance io_exception NoSuchFileException then Error.throw (File_Not_Found file) else - if Java.is_instance io_exception AccessDeniedException then Error.throw (Io_Error file "You do not have permission to access the file") else - Error.throw (Io_Error file "An IO error has occurred: "+io_exception.getMessage) + if Java.is_instance io_exception FileAlreadyExistsException then Error.throw (File_Already_Exists_Error file) else + if Java.is_instance io_exception AccessDeniedException then Error.throw (Io_Error file "You do not have permission to access the file") else + Error.throw (Io_Error file "An IO error has occurred: "+io_exception.getMessage) ## PRIVATE @@ -911,6 +862,9 @@ type File_Error - file: The file that doesn't exist. type File_Not_Found file + ## Indicates that a destination file already exists. + type File_Already_Exists_Error file + ## A generic IO error. Arguments: @@ -925,6 +879,7 @@ type File_Error to_display_text = case this of File_Not_Found file -> "The file at " + file.path + " does not exist." Io_Error file msg -> msg.to_text + " (" + file.path + ")." + File_Already_Exists_Error file -> "The file at "+file.path+" already exists." ## PRIVATE @@ -975,3 +930,29 @@ get_file path = @Builtin_Method "File.get_file" Gets the textual path to the user's system-defined home directory. user_home : Text user_home = @Builtin_Method "File.user_home" + + +## Writes (or appends) the specified bytes to the specified file. + The behavior specified in the `existing_file` parameter will be used if the + file exists. + + Arguments: + - path: The path to the target file. + - encoding: The encoding to use when writing the file. + - on_existing_file: Specifies how to proceed if the file already exists. + - on_problems: Specifies how to handle any encountered problems. + + If a character cannot be converted to a byte, an `Encoding_Error` is raised. + If `on_problems` is set to `Report_Warning` or `Ignore`, it is replaced with + a substitute (either � (if Unicode) or ? depending on the encoding). + Otherwise, the process is aborted. + If the path to the parent location cannot be found or the filename is + invalid, a `File_Not_Found` is raised. + If another error occurs, such as access denied, an `Io_Error` is raised. + Otherwise, the file is created with the encoded text written to it. +Text.write : (File|Text) -> Encoding -> Existing_File_Behavior -> Problem_Behavior -> Nothing ! Encoding_Error | Illegal_Argument_Error | File_Not_Found | Io_Error | File_Already_Exists_Error +Text.write path encoding=Encoding.utf_8 on_existing_file=Existing_File_Behavior.Backup on_problems=Report_Warning = + bytes = this.bytes encoding on_problems + file = here.new path + on_existing_file.write file stream-> + stream.write_bytes bytes diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/System/File/Existing_File_Behavior.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/System/File/Existing_File_Behavior.enso new file mode 100644 index 00000000000..e676ea33673 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/System/File/Existing_File_Behavior.enso @@ -0,0 +1,89 @@ +from Standard.Base import all + +import Standard.Base.System.File.Option +from Standard.Base.System.File import File_Already_Exists_Error, Io_Error, File_Not_Found + +## Specifies the behavior of a write operation when the destination file + already exists. +type Existing_File_Behavior + ## Replace the existing file in-place, with the new file. + + Note: There is a risk of data loss if a failure occurs during the write + operation. + type Overwrite + + ## Creates a backup of the existing file (by appending a `.bak` suffix to + the name) before replacing it with the new contents. + + Note: This requires sufficient storage to have two copies of the file. + If an existing `.bak` file exists, it will be replaced. + type Backup + + ## Appends data to the existing file. + type Append + + ## If the file already exists, a `File_Already_Exists_Error` error is + raised. + type Error + + ## PRIVATE + Runs the `action` which is given a file output stream and should write + the required contents to it. + + The handle is configured depending on the specified behavior, it may + point to a temporary file, for example. The stream may only be used while + the action is being executed and it should not be stored anywhere for + later. + + The `action` may not be run at all in case the `Error` behavior is + selected. + write : File -> (Output_Stream -> Nothing) -> Nothing ! File_Not_Found | Io_Error | File_Already_Exists_Error + write file action = + case this of + Overwrite -> file.with_output_stream [Option.Write, Option.Create, Option.Truncate_Existing] action + Append -> file.with_output_stream [Option.Write, Option.Create, Option.Append] action + Error -> file.with_output_stream [Option.Write, Option.Create_New] action + Backup -> Panic.recover [Io_Error, File_Not_Found] <| + handle_existing_file _ = + here.write_file_backing_up_old_one file action + ## We first attempt to write the file to the original + destination, but if that files due to the file already + existing, we will run the alternative algorithm which uses a + temporary file and creates a backup. + Panic.catch File_Already_Exists_Error handler=handle_existing_file <| + Panic.rethrow <| file.with_output_stream [Option.Write, Option.Create_New] action + +## PRIVATE +write_file_backing_up_old_one : File -> (Output_Stream -> Nothing) -> Nothing ! File_Not_Found | Io_Error | File_Already_Exists_Error +write_file_backing_up_old_one file action = Panic.recover [Io_Error, File_Not_Found] <| + parent = file.parent + bak_file = parent / file.name+".bak" + go i = + new_name = file.name + ".new" + if i == 0 then "" else "." + i.to_text + new_file = parent / new_name + handle_existing_file _ = go i+1 + handle_write_failure panic = + ## Since we were already inside of the write operation, + the file must have been created, but since we failed, we need to clean it up. + new_file.delete + Panic.throw panic.payload.cause + Panic.catch File_Already_Exists_Error handler=handle_existing_file <| + Panic.catch Internal_Write_Operation_Failed handler=handle_write_failure <| + Panic.rethrow <| + new_file.with_output_stream [Option.Write, Option.Create_New] output_stream-> + Panic.catch Any (action output_stream) caught_panic-> + Panic.throw (Internal_Write_Operation_Failed caught_panic) + ## We ignore the file not found error, because it means that there + is no file to back-up. This may also be caused by someone + removing the original file during the time when we have been + writing the new one to the temporary location. There is nothing + to back-up anymore, but this is not a failure, so it can be + safely ignored. + Panic.catch File_Not_Found handler=(_->Nothing) <| + Panic.rethrow <| file.move_to bak_file + Panic.rethrow <| new_file.move_to file + go 0 + + +## PRIVATE +type Internal_Write_Operation_Failed (cause : Caught_Panic) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso index ddbe1527aab..ec89de32555 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso @@ -1306,7 +1306,7 @@ type Table example_to_csv = Examples.inventory_table.write_csv (Enso_Project.data / 'example.json') write_json : File.File -> Nothing - write_json file = file.write_text this.to_json.to_text + write_json file = this.to_json.to_text.write file ## UNSTABLE diff --git a/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso index 5d69b1b6867..5838380168d 100644 --- a/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso @@ -712,7 +712,7 @@ wrap_junit_testsuites config builder ~action = if config.should_output_junit then builder.append '\n' config.output_path.parent.create_directory - config.output_path.write_text builder.toString + builder.toString.write config.output_path result diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoFile.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoFile.java index b83d77d973c..8a37c89dd73 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoFile.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoFile.java @@ -4,6 +4,7 @@ import com.oracle.truffle.api.TruffleFile; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.CopyOption; import java.nio.file.OpenOption; /** @@ -89,6 +90,10 @@ public class EnsoFile { truffleFile.delete(); } + public void move(EnsoFile target, CopyOption... options) throws IOException { + truffleFile.move(target.truffleFile, options); + } + public boolean startsWith(EnsoFile parent) { return truffleFile.startsWith(parent.truffleFile); } diff --git a/test/Table_Tests/data/transient/.gitignore b/test/Table_Tests/data/transient/.gitignore index afed0735dc9..074bb471511 100644 --- a/test/Table_Tests/data/transient/.gitignore +++ b/test/Table_Tests/data/transient/.gitignore @@ -1 +1 @@ -*.csv +*.csv* diff --git a/test/Table_Tests/src/Delimited_Read_Spec.enso b/test/Table_Tests/src/Delimited_Read_Spec.enso index e24a570ef1f..eccb34eca42 100644 --- a/test/Table_Tests/src/Delimited_Read_Spec.enso +++ b/test/Table_Tests/src/Delimited_Read_Spec.enso @@ -109,7 +109,7 @@ spec = create_file name ending_style = lines = ['a,b,c', 'd,e,f', '1,2,3'] text = lines.join ending_style - (path name).write_text text Encoding.utf_8 + text.write (path name) test_file name = table = File.read (path name) (Delimited "," headers=True value_formatter=Nothing) Problem_Behavior.Report_Error @@ -126,7 +126,7 @@ spec = test_file 'cr.csv' # Currently mixed line endings are not supported. - (path 'mixed.csv').write_text 'a,b,c\nd,e,f\r1,2,3' + 'a,b,c\nd,e,f\r1,2,3'.write (path 'mixed.csv') File.read (path 'mixed.csv') (Delimited "," headers=True value_formatter=Nothing) Problem_Behavior.Report_Error . should_fail_with Invalid_Row Test.specify "should work with Windows-1252 encoding" <| diff --git a/test/Tests/data/transient/.gitignore b/test/Tests/data/transient/.gitignore index 2211df63dd2..355c0232e85 100644 --- a/test/Tests/data/transient/.gitignore +++ b/test/Tests/data/transient/.gitignore @@ -1 +1 @@ -*.txt +*.txt* diff --git a/test/Tests/src/System/File_Spec.enso b/test/Tests/src/System/File_Spec.enso index 545a489a4f5..d84f8f0a38c 100644 --- a/test/Tests/src/System/File_Spec.enso +++ b/test/Tests/src/System/File_Spec.enso @@ -1,6 +1,8 @@ from Standard.Base import all from Standard.Base.Data.Text.Encoding as Encoding_Module import Encoding, Encoding_Error +import Standard.Base.System.File.Existing_File_Behavior +from Standard.Base.System.File import File_Already_Exists_Error import Standard.Test import Standard.Test.Problems @@ -46,7 +48,7 @@ spec = f = Enso_Project.data / "short.txt" f.delete_if_exists f.exists.should_be_false - f.write_text "Cup" + "Cup".write f on_existing_file=Existing_File_Behavior.Overwrite f.with_input_stream stream-> stream.read_byte.should_equal 67 stream.read_byte.should_equal 117 @@ -118,18 +120,111 @@ spec = contents_2.should .start_with "Cupcake ipsum dolor sit amet." Test.group "write operations" <| - Test.specify "should write and append to files" <| - f = Enso_Project.data / "work.txt" + transient = Enso_Project.data / "transient" + Test.specify "should allow to append to files" <| + f = transient / "work.txt" f.delete_if_exists f.exists.should_be_false - f.write_text "line 1!" + "line 1!".write f on_existing_file=Existing_File_Behavior.Append f.exists.should_be_true f.read_text.should_equal "line 1!" - f.append '\nline 2!' + '\nline 2!'.write f on_existing_file=Existing_File_Behavior.Append f.read_text.should_equal 'line 1!\nline 2!' f.delete f.exists.should_be_false + Test.specify "should allow to overwrite files" <| + f = transient / "work.txt" + f.delete_if_exists + f.exists.should_be_false + "line 1!".write f on_existing_file=Existing_File_Behavior.Overwrite . should_equal Nothing + f.exists.should_be_true + f.read_text.should_equal "line 1!" + 'line 2!'.write f on_existing_file=Existing_File_Behavior.Overwrite . should_equal Nothing + f.read_text.should_equal 'line 2!' + f.delete + f.exists.should_be_false + + Test.specify "should fail if a file already exists, depending on the settings" <| + f = transient / "work.txt" + f.delete_if_exists + f.exists.should_be_false + "line 1!".write f on_existing_file=Existing_File_Behavior.Error . should_equal Nothing + f.exists.should_be_true + f.read_text.should_equal "line 1!" + "line 2!".write f on_existing_file=Existing_File_Behavior.Error . should_fail_with File_Already_Exists_Error + f.read_text.should_equal 'line 1!' + f.delete + f.exists.should_be_false + + Test.specify "should create a backup when writing a file" <| + f = transient / "work.txt" + f.delete_if_exists + f.exists.should_be_false + "line 1!".write f . should_equal Nothing + if f.exists.not then + Test.fail "The file should have been created." + f.read_text.should_equal "line 1!" + + bak = transient / "work.txt.bak" + "backup content".write bak on_existing_file=Existing_File_Behavior.Overwrite + + n0 = transient / "work.txt.new" + n1 = transient / "work.txt.new.1" + n2 = transient / "work.txt.new.2" + n3 = transient / "work.txt.new.3" + n4 = transient / "work.txt.new.4" + written_news = [n0, n1, n2, n4] + written_news.each n-> + "new content".write n on_existing_file=Existing_File_Behavior.Overwrite + n3.delete_if_exists + + "line 2!".write f . should_equal Nothing + f.read_text.should_equal 'line 2!' + bak.read_text.should_equal 'line 1!' + if n3.exists then + Test.fail "The temporary file should have been cleaned up." + written_news.each n-> + n.read_text . should_equal "new content" + [f, bak, n0, n1, n2, n4].each .delete + + Test.specify "should correctly handle failure of the write operation when working with the backup" <| + f = transient / "work.txt" + "OLD".write f on_existing_file=Existing_File_Behavior.Overwrite + bak_file = transient / "work.txt.bak" + new_file = transient / "work.txt.new" + [bak_file, new_file].each .delete_if_exists + + result = Panic.recover Illegal_State_Error <| + Existing_File_Behavior.Backup.write f output_stream-> + output_stream.write_bytes "foo".utf_8 + Panic.throw (Illegal_State_Error "baz") + output_stream.write_bytes "bar".utf_8 + result.should_fail_with Illegal_State_Error + result.catch.message . should_equal "baz" + f.read_text . should_equal "OLD" + if bak_file.exists then + Test.fail "If the operation failed, we shouldn't have even created the backup." + if new_file.exists then + Test.fail "The temporary file should have been cleaned up." + + f.delete + result2 = Panic.recover Illegal_State_Error <| + Existing_File_Behavior.Backup.write f output_stream-> + output_stream.write_bytes "foo".utf_8 + Panic.throw (Illegal_State_Error "baz") + output_stream.write_bytes "bar".utf_8 + result2.should_fail_with Illegal_State_Error + result2.catch.message . should_equal "baz" + if f.exists.not then + Test.fail "Since we were writing to the original destination, the partially written file should have been preserved even upon failure." + f.read_text . should_equal "foo" + if bak_file.exists then + Test.fail "If the operation failed, we shouldn't have even created the backup." + if new_file.exists then + Test.fail "The temporary file should have been cleaned up." + f.delete + Test.group "folder operations" <| resolve files = base = Enso_Project.data diff --git a/test/Tests/src/System/Reporting_Stream_Decoder_Spec.enso b/test/Tests/src/System/Reporting_Stream_Decoder_Spec.enso index 428d85ada69..9a6844bf572 100644 --- a/test/Tests/src/System/Reporting_Stream_Decoder_Spec.enso +++ b/test/Tests/src/System/Reporting_Stream_Decoder_Spec.enso @@ -29,7 +29,7 @@ spec = f = Enso_Project.data / "short.txt" f.delete_if_exists f.exists.should_be_false - f.write_text "Cup" + "Cup".write f java_charset = Encoding.utf_8.to_java_charset f.with_input_stream [File.Option.Read] stream-> stream.with_java_stream java_stream-> @@ -45,7 +45,7 @@ spec = f = Enso_Project.data / "transient" / "varying_chunks.txt" fragment = 'Hello 😎🚀🚧!' contents = 1.up_to 1000 . map _->fragment . join '\n' - f.write_text contents + contents.write f java_charset = Encoding.utf_8.to_java_charset all_codepoints = Vector.new_builder @@ -104,7 +104,7 @@ spec = f = Enso_Project.data / "transient" / "utf8.txt" encoding = Encoding.utf_8 java_charset = encoding.to_java_charset - f.write_text ((0.up_to 100).map _->'Hello World!' . join '\n') Encoding.utf_8 + ((0.up_to 100).map _->'Hello World!' . join '\n').write f expected_contents = f.read_text contents = read_file_one_by_one f java_charset expected_contents.length contents.should_equal expected_contents