| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ppapi/tests/test_file_io.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "ppapi/c/pp_errors.h" |
| #include "ppapi/c/ppb_file_io.h" |
| #include "ppapi/c/private/pp_file_handle.h" |
| #include "ppapi/c/private/ppb_testing_private.h" |
| #include "ppapi/cpp/file_io.h" |
| #include "ppapi/cpp/file_ref.h" |
| #include "ppapi/cpp/file_system.h" |
| #include "ppapi/cpp/instance.h" |
| #include "ppapi/cpp/module.h" |
| #include "ppapi/cpp/private/file_io_private.h" |
| #include "ppapi/cpp/private/pass_file_handle.h" |
| #include "ppapi/tests/test_utils.h" |
| #include "ppapi/tests/testing_instance.h" |
| |
| #if defined(PPAPI_OS_WIN) |
| # include <io.h> |
| # include <windows.h> |
| // TODO(hamaji): Use standard windows APIs instead of compatibility layer? |
| # define lseek _lseek |
| # define read _read |
| # define write _write |
| # define ssize_t int |
| #else |
| # include <sys/mman.h> |
| # include <unistd.h> |
| #endif |
| |
| REGISTER_TEST_CASE(FileIO); |
| |
| namespace { |
| |
| std::string ReportMismatch(const std::string& method_name, |
| const std::string& returned_result, |
| const std::string& expected_result) { |
| return method_name + " returned '" + returned_result + "'; '" + |
| expected_result + "' expected."; |
| } |
| |
| std::string ReportOpenError(int32_t open_flags) { |
| static const char* kFlagNames[] = { |
| "PP_FILEOPENFLAG_READ", |
| "PP_FILEOPENFLAG_WRITE", |
| "PP_FILEOPENFLAG_CREATE", |
| "PP_FILEOPENFLAG_TRUNCATE", |
| "PP_FILEOPENFLAG_EXCLUSIVE" |
| }; |
| |
| std::string result = "FileIO:Open had unexpected behavior with flags: "; |
| bool first_flag = true; |
| for (int32_t mask = 1, index = 0; mask <= PP_FILEOPENFLAG_EXCLUSIVE; |
| mask <<= 1, ++index) { |
| if (mask & open_flags) { |
| if (first_flag) { |
| first_flag = false; |
| } else { |
| result += " | "; |
| } |
| result += kFlagNames[index]; |
| } |
| } |
| if (first_flag) |
| result += "[None]"; |
| |
| return result; |
| } |
| |
| int32_t ReadEntireFile(PP_Instance instance, |
| pp::FileIO* file_io, |
| int32_t offset, |
| std::string* data, |
| CallbackType callback_type) { |
| TestCompletionCallback callback(instance, callback_type); |
| char buf[256]; |
| int32_t read_offset = offset; |
| |
| for (;;) { |
| callback.WaitForResult( |
| file_io->Read(read_offset, buf, sizeof(buf), callback.GetCallback())); |
| if (callback.result() < 0) |
| return callback.result(); |
| if (callback.result() == 0) |
| break; |
| read_offset += callback.result(); |
| data->append(buf, callback.result()); |
| } |
| |
| return PP_OK; |
| } |
| |
| int32_t ReadToArrayEntireFile(PP_Instance instance, |
| pp::FileIO* file_io, |
| int32_t offset, |
| std::string* data, |
| CallbackType callback_type) { |
| TestCompletionCallbackWithOutput< std::vector<char> > callback( |
| instance, callback_type); |
| |
| for (;;) { |
| callback.WaitForResult(file_io->Read(offset, 256, callback.GetCallback())); |
| int32_t rv = callback.result(); |
| if (rv < 0) |
| return rv; |
| if (rv == 0) |
| break; |
| const std::vector<char>& output = callback.output(); |
| assert(rv == static_cast<int32_t>(output.size())); |
| offset += rv; |
| data->append(output.begin(), output.end()); |
| } |
| |
| return PP_OK; |
| } |
| |
| #if !defined(PPAPI_OS_WIN) |
| bool ReadEntireFileFromFileHandle(int fd, std::string* data) { |
| if (lseek(fd, 0, SEEK_SET) < 0) |
| return false; |
| data->clear(); |
| |
| int ret; |
| do { |
| char buf[8192]; |
| ret = read(fd, buf, sizeof(buf)); |
| if (ret > 0) |
| data->append(buf, ret); |
| } while (ret > 0); |
| return ret == 0; |
| } |
| #endif // !defined(PPAPI_OS_WIN) |
| |
| int32_t WriteEntireBuffer(PP_Instance instance, |
| pp::FileIO* file_io, |
| int32_t offset, |
| const std::string& data, |
| CallbackType callback_type) { |
| TestCompletionCallback callback(instance, callback_type); |
| int32_t write_offset = offset; |
| const char* buf = data.c_str(); |
| int32_t size = static_cast<int32_t>(data.size()); |
| |
| while (write_offset < offset + size) { |
| callback.WaitForResult(file_io->Write(write_offset, |
| &buf[write_offset - offset], |
| size - write_offset + offset, |
| callback.GetCallback())); |
| if (callback.result() < 0) |
| return callback.result(); |
| if (callback.result() == 0) |
| return PP_ERROR_FAILED; |
| write_offset += callback.result(); |
| } |
| |
| return PP_OK; |
| } |
| |
| } // namespace |
| |
| bool TestFileIO::Init() { |
| return CheckTestingInterface() && EnsureRunningOverHTTP(); |
| } |
| |
| void TestFileIO::RunTests(const std::string& filter) { |
| RUN_CALLBACK_TEST(TestFileIO, Open, filter); |
| RUN_CALLBACK_TEST(TestFileIO, OpenDirectory, filter); |
| RUN_CALLBACK_TEST(TestFileIO, ReadWriteSetLength, filter); |
| RUN_CALLBACK_TEST(TestFileIO, ReadToArrayWriteSetLength, filter); |
| RUN_CALLBACK_TEST(TestFileIO, TouchQuery, filter); |
| RUN_CALLBACK_TEST(TestFileIO, AbortCalls, filter); |
| RUN_CALLBACK_TEST(TestFileIO, ParallelReads, filter); |
| RUN_CALLBACK_TEST(TestFileIO, ParallelWrites, filter); |
| RUN_CALLBACK_TEST(TestFileIO, NotAllowMixedReadWrite, filter); |
| RUN_CALLBACK_TEST(TestFileIO, RequestOSFileHandle, filter); |
| RUN_CALLBACK_TEST(TestFileIO, RequestOSFileHandleWithOpenExclusive, filter); |
| RUN_CALLBACK_TEST(TestFileIO, Mmap, filter); |
| |
| // TODO(viettrungluu): add tests: |
| // - that PP_ERROR_PENDING is correctly returned |
| // - that operations respect the file open modes (flags) |
| } |
| |
| std::string TestFileIO::TestOpen() { |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| pp::FileRef file_ref(file_system, "/file_open"); |
| |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| std::string result; |
| result = MatchOpenExpectations( |
| &file_system, |
| PP_FILEOPENFLAG_READ, |
| DONT_CREATE_IF_DOESNT_EXIST | OPEN_IF_EXISTS | DONT_TRUNCATE_IF_EXISTS); |
| if (!result.empty()) |
| return result; |
| |
| // Test the behavior of the power set of |
| // { PP_FILEOPENFLAG_CREATE, |
| // PP_FILEOPENFLAG_TRUNCATE, |
| // PP_FILEOPENFLAG_EXCLUSIVE }. |
| |
| // First of all, none of them are specified. |
| result = MatchOpenExpectations( |
| &file_system, |
| PP_FILEOPENFLAG_WRITE, |
| DONT_CREATE_IF_DOESNT_EXIST | OPEN_IF_EXISTS | DONT_TRUNCATE_IF_EXISTS); |
| if (!result.empty()) |
| return result; |
| |
| result = MatchOpenExpectations( |
| &file_system, |
| PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_CREATE, |
| CREATE_IF_DOESNT_EXIST | OPEN_IF_EXISTS | DONT_TRUNCATE_IF_EXISTS); |
| if (!result.empty()) |
| return result; |
| |
| result = MatchOpenExpectations( |
| &file_system, |
| PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_EXCLUSIVE, |
| DONT_CREATE_IF_DOESNT_EXIST | OPEN_IF_EXISTS | DONT_TRUNCATE_IF_EXISTS); |
| if (!result.empty()) |
| return result; |
| |
| result = MatchOpenExpectations( |
| &file_system, |
| PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_TRUNCATE, |
| DONT_CREATE_IF_DOESNT_EXIST | OPEN_IF_EXISTS | TRUNCATE_IF_EXISTS); |
| if (!result.empty()) |
| return result; |
| |
| result = MatchOpenExpectations( |
| &file_system, |
| PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_EXCLUSIVE, |
| CREATE_IF_DOESNT_EXIST | DONT_OPEN_IF_EXISTS | DONT_TRUNCATE_IF_EXISTS); |
| if (!result.empty()) |
| return result; |
| |
| result = MatchOpenExpectations( |
| &file_system, |
| PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_CREATE | PP_FILEOPENFLAG_TRUNCATE, |
| CREATE_IF_DOESNT_EXIST | OPEN_IF_EXISTS | TRUNCATE_IF_EXISTS); |
| if (!result.empty()) |
| return result; |
| |
| result = MatchOpenExpectations( |
| &file_system, |
| PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_EXCLUSIVE | |
| PP_FILEOPENFLAG_TRUNCATE, |
| DONT_CREATE_IF_DOESNT_EXIST | OPEN_IF_EXISTS | TRUNCATE_IF_EXISTS); |
| if (!result.empty()) |
| return result; |
| |
| result = MatchOpenExpectations( |
| &file_system, |
| PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_EXCLUSIVE | PP_FILEOPENFLAG_TRUNCATE, |
| CREATE_IF_DOESNT_EXIST | DONT_OPEN_IF_EXISTS | DONT_TRUNCATE_IF_EXISTS); |
| if (!result.empty()) |
| return result; |
| |
| // Invalid combination: PP_FILEOPENFLAG_TRUNCATE without |
| // PP_FILEOPENFLAG_WRITE. |
| result = MatchOpenExpectations( |
| &file_system, |
| PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_TRUNCATE, |
| INVALID_FLAG_COMBINATION); |
| if (!result.empty()) |
| return result; |
| |
| PASS(); |
| } |
| |
| std::string TestFileIO::TestOpenDirectory() { |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Make a directory. |
| pp::FileRef dir_ref(file_system, "/test_dir_open_directory"); |
| callback.WaitForResult(dir_ref.MakeDirectory( |
| PP_MAKEDIRECTORYFLAG_NONE, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Open the directory. This is expected to fail since directories cannot be |
| // opened. |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(dir_ref, PP_FILEOPENFLAG_READ, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_ERROR_NOTAFILE, callback.result()); |
| |
| PASS(); |
| } |
| |
| std::string TestFileIO::TestReadWriteSetLength() { |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| pp::FileRef file_ref(file_system, "/file_read_write_setlength"); |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_TRUNCATE | |
| PP_FILEOPENFLAG_READ | |
| PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Write something to the file. |
| int32_t rv = WriteEntireBuffer(instance_->pp_instance(), &file_io, 0, |
| "test_test", callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| |
| // Attempt to read a negative number of bytes; it should fail. |
| char buf[256]; |
| callback.WaitForResult(file_io.Read(0, |
| buf, |
| -1, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_ERROR_FAILED, callback.result()); |
| |
| // Read the entire file. |
| std::string read_buffer; |
| rv = ReadEntireFile(instance_->pp_instance(), &file_io, 0, &read_buffer, |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("test_test"), read_buffer); |
| |
| // Truncate the file. |
| callback.WaitForResult(file_io.SetLength(4, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Check the file contents. |
| read_buffer.clear(); |
| rv = ReadEntireFile(instance_->pp_instance(), &file_io, 0, &read_buffer, |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("test"), read_buffer); |
| |
| // Try to read past the end of the file. |
| read_buffer.clear(); |
| rv = ReadEntireFile(instance_->pp_instance(), &file_io, 100, &read_buffer, |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_TRUE(read_buffer.empty()); |
| |
| // Write past the end of the file. The file should be zero-padded. |
| rv = WriteEntireBuffer(instance_->pp_instance(), &file_io, 8, "test", |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| |
| // Check the contents of the file. |
| read_buffer.clear(); |
| rv = ReadEntireFile(instance_->pp_instance(), &file_io, 0, &read_buffer, |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("test\0\0\0\0test", 12), read_buffer); |
| |
| // Extend the file. |
| callback.WaitForResult(file_io.SetLength(16, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Check the contents of the file. |
| read_buffer.clear(); |
| rv = ReadEntireFile(instance_->pp_instance(), &file_io, 0, &read_buffer, |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("test\0\0\0\0test\0\0\0\0", 16), read_buffer); |
| |
| // Write in the middle of the file. |
| rv = WriteEntireBuffer(instance_->pp_instance(), &file_io, 4, "test", |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| |
| // Check the contents of the file. |
| read_buffer.clear(); |
| rv = ReadEntireFile(instance_->pp_instance(), &file_io, 0, &read_buffer, |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("testtesttest\0\0\0\0", 16), read_buffer); |
| |
| // Read from the middle of the file. |
| read_buffer.clear(); |
| rv = ReadEntireFile(instance_->pp_instance(), &file_io, 4, &read_buffer, |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("testtest\0\0\0\0", 12), read_buffer); |
| |
| // Append to the end of the file. |
| pp::FileIO file_io2(instance_); |
| callback.WaitForResult(file_io2.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_READ | |
| PP_FILEOPENFLAG_APPEND, |
| callback.GetCallback())); |
| rv = WriteEntireBuffer(instance_->pp_instance(), &file_io2, 0, "appended", |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| read_buffer.clear(); |
| rv = ReadEntireFile(instance_->pp_instance(), &file_io2, 0, &read_buffer, |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("testtesttest\0\0\0\0appended", 24), read_buffer); |
| |
| PASS(); |
| } |
| |
| // This is basically a copy of TestReadWriteSetLength, but with the new Read |
| // API. With this test case, we can make sure the two Read's have the same |
| // behavior. |
| std::string TestFileIO::TestReadToArrayWriteSetLength() { |
| if (callback_type() == PP_BLOCKING) { |
| // This test does not make sense for blocking callbacks. |
| PASS(); |
| } |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| pp::FileRef file_ref(file_system, "/file_read_write_setlength"); |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_TRUNCATE | |
| PP_FILEOPENFLAG_READ | |
| PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Write something to the file. |
| int32_t rv = WriteEntireBuffer(instance_->pp_instance(), &file_io, 0, |
| "test_test", callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| |
| TestCompletionCallbackWithOutput< std::vector<char> > callback2( |
| instance_->pp_instance(), callback_type()); |
| // Attempt to read a negative number of bytes; it should fail. |
| callback2.WaitForResult(file_io.Read(0, -1, callback2.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback2); |
| ASSERT_EQ(PP_ERROR_FAILED, callback2.result()); |
| |
| // Read the entire file. |
| std::string read_buffer; |
| read_buffer.reserve(10); |
| rv = ReadToArrayEntireFile(instance_->pp_instance(), &file_io, 0, |
| &read_buffer, callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("test_test"), read_buffer); |
| |
| // Truncate the file. |
| callback.WaitForResult(file_io.SetLength(4, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, rv); |
| |
| // Check the file contents. |
| read_buffer.clear(); |
| rv = ReadToArrayEntireFile(instance_->pp_instance(), &file_io, 0, |
| &read_buffer, callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("test"), read_buffer); |
| |
| // Try to read past the end of the file. |
| read_buffer.clear(); |
| rv = ReadToArrayEntireFile(instance_->pp_instance(), &file_io, 100, |
| &read_buffer, callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_TRUE(read_buffer.empty()); |
| |
| // Write past the end of the file. The file should be zero-padded. |
| rv = WriteEntireBuffer(instance_->pp_instance(), &file_io, 8, "test", |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| |
| // Check the contents of the file. |
| read_buffer.clear(); |
| rv = ReadToArrayEntireFile(instance_->pp_instance(), &file_io, 0, |
| &read_buffer, callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("test\0\0\0\0test", 12), read_buffer); |
| |
| // Extend the file. |
| callback.WaitForResult(file_io.SetLength(16, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Check the contents of the file. |
| read_buffer.clear(); |
| rv = ReadToArrayEntireFile(instance_->pp_instance(), &file_io, 0, |
| &read_buffer, callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("test\0\0\0\0test\0\0\0\0", 16), read_buffer); |
| |
| // Write in the middle of the file. |
| rv = WriteEntireBuffer(instance_->pp_instance(), &file_io, 4, "test", |
| callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| |
| // Check the contents of the file. |
| read_buffer.clear(); |
| rv = ReadToArrayEntireFile(instance_->pp_instance(), &file_io, 0, |
| &read_buffer, callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("testtesttest\0\0\0\0", 16), read_buffer); |
| |
| // Read from the middle of the file. |
| read_buffer.clear(); |
| rv = ReadToArrayEntireFile(instance_->pp_instance(), &file_io, 4, |
| &read_buffer, callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("testtest\0\0\0\0", 12), read_buffer); |
| |
| PASS(); |
| } |
| |
| std::string TestFileIO::TestTouchQuery() { |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| pp::FileRef file_ref(file_system, "/file_touch"); |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_TRUNCATE | |
| PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Write some data to have a non-zero file size. |
| callback.WaitForResult(file_io.Write(0, "test", 4, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(4, callback.result()); |
| |
| const PP_Time last_access_time = 123 * 24 * 3600.0; |
| // last_modified_time's granularity is 2 seconds |
| // NOTE: In NaCl on Windows, NaClDescIO uses _fstat64 to retrieve file info. |
| // This function returns strange values for very small time values (near the |
| // Unix Epoch). For a value like 246.0, it returns -1. For larger values, it |
| // returns values that are exactly an hour less. The value below is handled |
| // correctly, and is only 100 days after the start of Unix time. |
| const PP_Time last_modified_time = 100 * 24 * 3600.0; |
| callback.WaitForResult(file_io.Touch(last_access_time, last_modified_time, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| PP_FileInfo info; |
| callback.WaitForResult(file_io.Query(&info, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| if ((info.size != 4) || |
| (info.type != PP_FILETYPE_REGULAR) || |
| (info.system_type != PP_FILESYSTEMTYPE_LOCALTEMPORARY)) |
| // Disabled due to DST-related failure: crbug.com/314579 |
| //(info.last_access_time != last_access_time) || |
| //(info.last_modified_time != last_modified_time)) |
| return "FileIO::Query() has returned bad data."; |
| |
| // Call |Query()| again, to make sure it works a second time. |
| callback.WaitForResult(file_io.Query(&info, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| PASS(); |
| } |
| |
| std::string TestFileIO::TestAbortCalls() { |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| pp::FileRef file_ref(file_system, "/file_abort_calls"); |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| int32_t rv = PP_ERROR_FAILED; |
| // First, create a file on which to do ops. |
| { |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult( |
| file_io.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // N.B.: Should write at least 3 bytes. |
| rv = WriteEntireBuffer(instance_->pp_instance(), &file_io, 0, |
| "foobarbazquux", callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| } |
| |
| // Abort |Open()|. |
| { |
| rv = pp::FileIO(instance_) |
| .Open(file_ref, PP_FILEOPENFLAG_READ, callback.GetCallback()); |
| } |
| callback.WaitForAbortResult(rv); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| |
| // Abort |Query()|. |
| { |
| PP_FileInfo info = { 0 }; |
| // Save a copy and make sure |info| doesn't get written to if it is aborted. |
| PP_FileInfo info_copy; |
| memcpy(&info_copy, &info, sizeof(info)); |
| { |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, PP_FILEOPENFLAG_READ, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| rv = file_io.Query(&info, callback.GetCallback()); |
| } // Destroy |file_io|. |
| callback.WaitForResult(rv); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| if (callback_type() == PP_BLOCKING) { |
| ASSERT_EQ(PP_OK, callback.result()); |
| // The operation completed synchronously, so |info| should have changed. |
| ASSERT_NE(0, memcmp(&info_copy, &info, sizeof(info))); |
| } else { |
| ASSERT_EQ(PP_ERROR_ABORTED, callback.result()); |
| ASSERT_EQ(0, memcmp(&info_copy, &info, sizeof(info))); |
| } |
| } |
| |
| // Abort |Touch()|. |
| { |
| { |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| rv = file_io.Touch(0, 0, callback.GetCallback()); |
| } // Destroy |file_io|. |
| callback.WaitForAbortResult(rv); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| } |
| |
| // Abort |Read()|. |
| { |
| char buf[3] = { 0 }; |
| { |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, PP_FILEOPENFLAG_READ, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| rv = file_io.Read(0, buf, sizeof(buf), callback.GetCallback()); |
| } // Destroy |file_io|. |
| // Save a copy to make sure buf isn't written to in the async case. |
| char buf_copy[3]; |
| memcpy(&buf_copy, &buf, sizeof(buf)); |
| callback.WaitForResult(rv); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| if (callback_type() == PP_BLOCKING) { |
| ASSERT_EQ(callback.result(), sizeof(buf)); |
| } else { |
| ASSERT_EQ(PP_ERROR_ABORTED, callback.result()); |
| ASSERT_EQ(0, memcmp(&buf_copy, &buf, sizeof(buf))); |
| } |
| } |
| |
| // Abort |Write()|. |
| { |
| char buf[3] = { 0 }; |
| { |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| rv = file_io.Write(0, buf, sizeof(buf), callback.GetCallback()); |
| } // Destroy |file_io|. |
| callback.WaitForResult(rv); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| if (callback_type() == PP_BLOCKING) |
| ASSERT_EQ(callback.result(), sizeof(buf)); |
| else |
| ASSERT_EQ(PP_ERROR_ABORTED, callback.result()); |
| } |
| |
| // Abort |SetLength()|. |
| { |
| { |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| rv = file_io.SetLength(3, callback.GetCallback()); |
| } // Destroy |file_io|. |
| callback.WaitForAbortResult(rv); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| } |
| |
| // Abort |Flush|. |
| { |
| { |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| rv = file_io.Flush(callback.GetCallback()); |
| } // Destroy |file_io|. |
| callback.WaitForAbortResult(rv); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| } |
| |
| PASS(); |
| } |
| |
| std::string TestFileIO::TestParallelReads() { |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| pp::FileRef file_ref(file_system, "/file_parallel_reads"); |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_TRUNCATE | |
| PP_FILEOPENFLAG_READ | |
| PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Set up testing contents. |
| int32_t rv = WriteEntireBuffer(instance_->pp_instance(), &file_io, 0, |
| "abcdefghijkl", callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| |
| // Parallel read operations. |
| const char* border = "__border__"; |
| const int32_t border_size = static_cast<int32_t>(strlen(border)); |
| |
| TestCompletionCallback callback_1(instance_->pp_instance(), callback_type()); |
| int32_t read_offset_1 = 0; |
| int32_t size_1 = 3; |
| std::vector<char> extended_buf_1(border_size * 2 + size_1); |
| char* buf_1 = &extended_buf_1[border_size]; |
| memcpy(&extended_buf_1[0], border, border_size); |
| memcpy(buf_1 + size_1, border, border_size); |
| |
| TestCompletionCallback callback_2(instance_->pp_instance(), callback_type()); |
| int32_t read_offset_2 = size_1; |
| int32_t size_2 = 9; |
| std::vector<char> extended_buf_2(border_size * 2 + size_2); |
| char* buf_2 = &extended_buf_2[border_size]; |
| memcpy(&extended_buf_2[0], border, border_size); |
| memcpy(buf_2 + size_2, border, border_size); |
| |
| int32_t rv_1 = PP_OK; |
| int32_t rv_2 = PP_OK; |
| while (size_1 >= 0 && size_2 >= 0 && size_1 + size_2 > 0) { |
| if (size_1 > 0) { |
| rv_1 = file_io.Read(read_offset_1, buf_1, size_1, |
| callback_1.GetCallback()); |
| } |
| if (size_2 > 0) { |
| rv_2 = file_io.Read(read_offset_2, buf_2, size_2, |
| callback_2.GetCallback()); |
| } |
| if (size_1 > 0) { |
| callback_1.WaitForResult(rv_1); |
| CHECK_CALLBACK_BEHAVIOR(callback_1); |
| ASSERT_TRUE(callback_1.result() > 0); |
| read_offset_1 += callback_1.result(); |
| buf_1 += callback_1.result(); |
| size_1 -= callback_1.result(); |
| } |
| |
| if (size_2 > 0) { |
| callback_2.WaitForResult(rv_2); |
| CHECK_CALLBACK_BEHAVIOR(callback_2); |
| ASSERT_TRUE(callback_2.result() > 0); |
| read_offset_2 += callback_2.result(); |
| buf_2 += callback_2.result(); |
| size_2 -= callback_2.result(); |
| } |
| } |
| |
| // If |size_1| or |size_2| is not 0, we have invoked wrong callback(s). |
| ASSERT_EQ(0, size_1); |
| ASSERT_EQ(0, size_2); |
| |
| // Make sure every read operation writes into the correct buffer. |
| const char expected_result_1[] = "__border__abc__border__"; |
| const char expected_result_2[] = "__border__defghijkl__border__"; |
| ASSERT_TRUE(strncmp(&extended_buf_1[0], expected_result_1, |
| strlen(expected_result_1)) == 0); |
| ASSERT_TRUE(strncmp(&extended_buf_2[0], expected_result_2, |
| strlen(expected_result_2)) == 0); |
| PASS(); |
| } |
| |
| std::string TestFileIO::TestParallelWrites() { |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| pp::FileRef file_ref(file_system, "/file_parallel_writes"); |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_TRUNCATE | |
| PP_FILEOPENFLAG_READ | |
| PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Parallel write operations. |
| TestCompletionCallback callback_1(instance_->pp_instance(), callback_type()); |
| int32_t write_offset_1 = 0; |
| const char* buf_1 = "abc"; |
| int32_t size_1 = static_cast<int32_t>(strlen(buf_1)); |
| |
| TestCompletionCallback callback_2(instance_->pp_instance(), callback_type()); |
| int32_t write_offset_2 = size_1; |
| const char* buf_2 = "defghijkl"; |
| int32_t size_2 = static_cast<int32_t>(strlen(buf_2)); |
| |
| int32_t rv_1 = PP_OK; |
| int32_t rv_2 = PP_OK; |
| while (size_1 >= 0 && size_2 >= 0 && size_1 + size_2 > 0) { |
| if (size_1 > 0) { |
| // Copy the buffer so we can erase it below. |
| std::string str_1(buf_1); |
| rv_1 = file_io.Write( |
| write_offset_1, &str_1[0], static_cast<int32_t>(str_1.size()), |
| callback_1.GetCallback()); |
| // Erase the buffer to test that async writes copy it. |
| std::fill(str_1.begin(), str_1.end(), 0); |
| } |
| if (size_2 > 0) { |
| // Copy the buffer so we can erase it below. |
| std::string str_2(buf_2); |
| rv_2 = file_io.Write( |
| write_offset_2, &str_2[0], static_cast<int32_t>(str_2.size()), |
| callback_2.GetCallback()); |
| // Erase the buffer to test that async writes copy it. |
| std::fill(str_2.begin(), str_2.end(), 0); |
| } |
| |
| if (size_1 > 0) { |
| callback_1.WaitForResult(rv_1); |
| CHECK_CALLBACK_BEHAVIOR(callback_1); |
| ASSERT_TRUE(callback_1.result() > 0); |
| write_offset_1 += callback_1.result(); |
| buf_1 += callback_1.result(); |
| size_1 -= callback_1.result(); |
| } |
| |
| if (size_2 > 0) { |
| callback_2.WaitForResult(rv_2); |
| CHECK_CALLBACK_BEHAVIOR(callback_2); |
| ASSERT_TRUE(callback_2.result() > 0); |
| write_offset_2 += callback_2.result(); |
| buf_2 += callback_2.result(); |
| size_2 -= callback_2.result(); |
| } |
| } |
| |
| // If |size_1| or |size_2| is not 0, we have invoked wrong callback(s). |
| ASSERT_EQ(0, size_1); |
| ASSERT_EQ(0, size_2); |
| |
| // Check the file contents. |
| std::string read_buffer; |
| int32_t rv = ReadEntireFile(instance_->pp_instance(), &file_io, 0, |
| &read_buffer, callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| ASSERT_EQ(std::string("abcdefghijkl"), read_buffer); |
| |
| PASS(); |
| } |
| |
| std::string TestFileIO::TestNotAllowMixedReadWrite() { |
| if (callback_type() == PP_BLOCKING) { |
| // This test does not make sense for blocking callbacks. |
| PASS(); |
| } |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| pp::FileRef file_ref(file_system, "/file_not_allow_mixed_read_write"); |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| pp::FileIO file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_TRUNCATE | |
| PP_FILEOPENFLAG_READ | |
| PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| TestCompletionCallback callback_1(instance_->pp_instance(), PP_REQUIRED); |
| int32_t write_offset_1 = 0; |
| const char* buf_1 = "mnopqrstuvw"; |
| int32_t rv_1 = file_io.Write(write_offset_1, buf_1, |
| static_cast<int32_t>(strlen(buf_1)), |
| callback_1.GetCallback()); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, rv_1); |
| |
| TestCompletionCallback callback_2(instance_->pp_instance(), callback_type()); |
| int32_t read_offset_2 = 4; |
| char buf_2[3]; |
| callback_2.WaitForResult(file_io.Read(read_offset_2, buf_2, sizeof(buf_2), |
| callback_2.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback_2); |
| ASSERT_EQ(PP_ERROR_INPROGRESS, callback_2.result()); |
| callback_1.WaitForResult(rv_1); |
| CHECK_CALLBACK_BEHAVIOR(callback_1); |
| |
| // Cannot query while a write is pending. |
| rv_1 = file_io.Write(write_offset_1, buf_1, |
| static_cast<int32_t>(strlen(buf_1)), |
| callback_1.GetCallback()); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, rv_1); |
| PP_FileInfo info; |
| callback_2.WaitForResult(file_io.Query(&info, callback_2.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback_2); |
| ASSERT_EQ(PP_ERROR_INPROGRESS, callback_2.result()); |
| callback_1.WaitForResult(rv_1); |
| CHECK_CALLBACK_BEHAVIOR(callback_1); |
| |
| // Cannot touch while a write is pending. |
| rv_1 = file_io.Write(write_offset_1, buf_1, |
| static_cast<int32_t>(strlen(buf_1)), |
| callback_1.GetCallback()); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, rv_1); |
| callback_2.WaitForResult(file_io.Touch(1234.0, 5678.0, |
| callback_2.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback_2); |
| ASSERT_EQ(PP_ERROR_INPROGRESS, callback_2.result()); |
| callback_1.WaitForResult(rv_1); |
| CHECK_CALLBACK_BEHAVIOR(callback_1); |
| |
| // Cannot set length while a write is pending. |
| rv_1 = file_io.Write(write_offset_1, buf_1, |
| static_cast<int32_t>(strlen(buf_1)), |
| callback_1.GetCallback()); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, rv_1); |
| callback_2.WaitForResult(file_io.SetLength(123, callback_2.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback_2); |
| ASSERT_EQ(PP_ERROR_INPROGRESS, callback_2.result()); |
| callback_1.WaitForResult(rv_1); |
| CHECK_CALLBACK_BEHAVIOR(callback_1); |
| |
| PASS(); |
| } |
| |
| std::string TestFileIO::TestRequestOSFileHandle() { |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| pp::FileRef file_ref(file_system, "/file_os_fd"); |
| |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| pp::FileIO_Private file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_TRUNCATE | |
| PP_FILEOPENFLAG_READ | |
| PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| TestCompletionCallbackWithOutput<pp::PassFileHandle> output_callback( |
| instance_->pp_instance(), callback_type()); |
| output_callback.WaitForResult( |
| file_io.RequestOSFileHandle(output_callback.GetCallback())); |
| PP_FileHandle handle = output_callback.output().Release(); |
| ASSERT_EQ(PP_OK, output_callback.result()); |
| |
| if (handle == PP_kInvalidFileHandle) |
| return "FileIO::RequestOSFileHandle() returned a bad file handle."; |
| #if defined(PPAPI_OS_WIN) |
| int fd = _open_osfhandle(reinterpret_cast<intptr_t>(handle), |
| _O_RDWR | _O_BINARY); |
| #else |
| int fd = handle; |
| #endif |
| if (fd < 0) |
| return "FileIO::RequestOSFileHandle() returned a bad file descriptor."; |
| |
| // Check write(2) for the native FD. |
| const std::string msg = "foobar"; |
| ssize_t cnt = write(fd, msg.data(), static_cast<unsigned>(msg.size())); |
| if (cnt < 0) |
| return ReportError("write for native FD returned error", errno); |
| if (cnt != static_cast<ssize_t>(msg.size())) |
| return ReportError("write for native FD count mismatch", cnt); |
| |
| // Check lseek(2) for the native FD. |
| off_t off = lseek(fd, 0, SEEK_CUR); |
| if (off == static_cast<off_t>(-1)) |
| return ReportError("lseek for native FD returned error", errno); |
| if (off != static_cast<off_t>(msg.size())) |
| return ReportError("lseek for native FD offset mismatch", off); |
| |
| off = lseek(fd, 0, SEEK_SET); |
| if (off == static_cast<off_t>(-1)) |
| return ReportError("lseek for native FD returned error", errno); |
| if (off != 0) |
| return ReportError("lseek for native FD offset mismatch", off); |
| |
| // Check read(2) for the native FD. |
| std::string buf(msg.size(), '\0'); |
| cnt = read(fd, &buf[0], static_cast<unsigned>(msg.size())); |
| if (cnt < 0) |
| return ReportError("read for native FD returned error", errno); |
| if (cnt != static_cast<ssize_t>(msg.size())) |
| return ReportError("read for native FD count mismatch", cnt); |
| if (msg != buf) |
| return ReportMismatch("read for native FD", buf, msg); |
| PASS(); |
| } |
| |
| // Calling RequestOSFileHandle with the FileIO that is opened with |
| // PP_FILEOPENFLAG_EXCLUSIVE used to cause NaCl module to crash while loading. |
| // This is a regression test for crbug.com/243241. |
| std::string TestFileIO::TestRequestOSFileHandleWithOpenExclusive() { |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| pp::FileRef file_ref(file_system, "/file_os_fd2"); |
| |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| // Open with PP_FILEOPENFLAG_CREATE and PP_FILEOPENFLAG_EXCLUSIVE will fail |
| // if the file already exists. Delete it here to make sure it does not. |
| callback.WaitForResult(file_ref.Delete(callback.GetCallback())); |
| |
| pp::FileIO_Private file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_READ | |
| PP_FILEOPENFLAG_WRITE | |
| PP_FILEOPENFLAG_EXCLUSIVE, |
| callback.GetCallback())); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| TestCompletionCallbackWithOutput<pp::PassFileHandle> output_callback( |
| instance_->pp_instance(), callback_type()); |
| output_callback.WaitForResult( |
| file_io.RequestOSFileHandle(output_callback.GetCallback())); |
| PP_FileHandle handle = output_callback.output().Release(); |
| if (handle == PP_kInvalidFileHandle) |
| return "FileIO::RequestOSFileHandle() returned a bad file handle."; |
| ASSERT_EQ(PP_OK, output_callback.result()); |
| |
| PASS(); |
| } |
| |
| std::string TestFileIO::TestMmap() { |
| #if !defined(PPAPI_OS_WIN) |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); |
| pp::FileRef file_ref(file_system, "/file_os_fd"); |
| |
| callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| pp::FileIO_Private file_io(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, |
| PP_FILEOPENFLAG_CREATE | |
| PP_FILEOPENFLAG_TRUNCATE | |
| PP_FILEOPENFLAG_READ | |
| PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| TestCompletionCallbackWithOutput<pp::PassFileHandle> output_callback( |
| instance_->pp_instance(), callback_type()); |
| output_callback.WaitForResult( |
| file_io.RequestOSFileHandle(output_callback.GetCallback())); |
| PP_FileHandle handle = output_callback.output().Release(); |
| ASSERT_EQ(PP_OK, output_callback.result()); |
| |
| if (handle == PP_kInvalidFileHandle) |
| return "FileIO::RequestOSFileHandle() returned a bad file handle."; |
| int fd = handle; |
| if (fd < 0) |
| return "FileIO::RequestOSFileHandle() returned a bad file descriptor."; |
| |
| // Check write(2) for the native FD. |
| const std::string msg = "foobar"; |
| ssize_t cnt = write(fd, msg.data(), msg.size()); |
| if (cnt < 0) |
| return ReportError("write for native FD returned error", errno); |
| if (cnt != static_cast<ssize_t>(msg.size())) |
| return ReportError("write for native FD count mismatch", cnt); |
| |
| // BEGIN mmap(2) test with a file handle opened in READ-WRITE mode. |
| // Check mmap(2) for read. |
| { |
| char* mapped = reinterpret_cast<char*>( |
| mmap(NULL, msg.size(), PROT_READ, MAP_PRIVATE, fd, 0)); |
| if (mapped == MAP_FAILED) |
| return ReportError("mmap(r) for native FD returned errno", errno); |
| // Make sure the buffer is cleared. |
| std::string buf = std::string(msg.size(), '\0'); |
| memcpy(&buf[0], mapped, msg.size()); |
| if (msg != buf) |
| return ReportMismatch("mmap(r) for native FD", buf, msg); |
| int r = munmap(mapped, msg.size()); |
| if (r < 0) |
| return ReportError("munmap for native FD returned error", errno); |
| } |
| |
| // Check mmap(2) for write with MAP_PRIVATE |
| { |
| char* mapped = reinterpret_cast<char*>( |
| mmap(NULL, msg.size(), PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)); |
| if (mapped == MAP_FAILED) |
| return ReportError("mmap(r) for native FD returned errno", errno); |
| // Make sure the file is not polluted by writing to privage mmap. |
| strncpy(mapped, "baz", 3); |
| std::string read_buffer; |
| ASSERT_TRUE(ReadEntireFileFromFileHandle(fd, &read_buffer)); |
| if (msg != read_buffer) |
| return ReportMismatch("file content != msg", read_buffer, msg); |
| int r = munmap(mapped, msg.size()); |
| if (r < 0) |
| return ReportError("munmap for native FD returned error", errno); |
| } |
| |
| // Check mmap(2) for write with MAP_SHARED. |
| { |
| char* mapped = reinterpret_cast<char*>( |
| mmap(NULL, msg.size(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); |
| if (mapped == MAP_FAILED) |
| return ReportError("mmap(w) for native FD returned errno", errno); |
| // s/foo/baz/ |
| strncpy(mapped, "baz", 3); |
| std::string read_buffer; |
| ASSERT_TRUE(ReadEntireFileFromFileHandle(fd, &read_buffer)); |
| if (read_buffer != "bazbar") |
| return ReportMismatch("file content != msg", read_buffer, "bazbar"); |
| int r = munmap(mapped, msg.size()); |
| if (r < 0) |
| return ReportError("munmap for native FD returned error", errno); |
| } |
| // END mmap(2) test with a file handle opened in READ-WRITE mode. |
| |
| if (close(fd) < 0) |
| return ReportError("close for native FD returned error", errno); |
| |
| // BEGIN mmap(2) test with a file handle opened in READONLY mode. |
| file_io = pp::FileIO_Private(instance_); |
| callback.WaitForResult(file_io.Open(file_ref, |
| PP_FILEOPENFLAG_READ, |
| callback.GetCallback())); |
| ASSERT_EQ(PP_OK, callback.result()); |
| |
| output_callback = TestCompletionCallbackWithOutput<pp::PassFileHandle>( |
| instance_->pp_instance(), callback_type()); |
| output_callback.WaitForResult( |
| file_io.RequestOSFileHandle(output_callback.GetCallback())); |
| handle = output_callback.output().Release(); |
| ASSERT_EQ(PP_OK, output_callback.result()); |
| |
| if (handle == PP_kInvalidFileHandle) |
| return "FileIO::RequestOSFileHandle() returned a bad file handle."; |
| fd = handle; |
| if (fd < 0) |
| return "FileIO::RequestOSFileHandle() returned a bad file descriptor."; |
| |
| const std::string msg2 = "bazbar"; |
| |
| // Check mmap(2) for read. |
| { |
| char* mapped = reinterpret_cast<char*>( |
| mmap(NULL, msg2.size(), PROT_READ, MAP_PRIVATE, fd, 0)); |
| if (mapped == MAP_FAILED) |
| return ReportError("mmap(r) for native FD returned errno", errno); |
| // Make sure the buffer is cleared. |
| std::string buf = std::string(msg2.size(), '\0'); |
| memcpy(&buf[0], mapped, msg2.size()); |
| if (msg2 != buf) |
| return ReportMismatch("mmap(r) for native FD", buf, msg2); |
| int r = munmap(mapped, msg2.size()); |
| if (r < 0) |
| return ReportError("munmap for native FD returned error", errno); |
| } |
| |
| // Check mmap(2) for write with MAP_PRIVATE |
| { |
| char* mapped = reinterpret_cast<char*>( |
| mmap(NULL, msg2.size(), PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)); |
| if (mapped == MAP_FAILED) |
| return ReportError("mmap(r) for native FD returned errno", errno); |
| // Make sure the file is not polluted by writing to privage mmap. |
| strncpy(mapped, "baz", 3); |
| std::string read_buffer; |
| ASSERT_TRUE(ReadEntireFileFromFileHandle(fd, &read_buffer)); |
| if (msg2 != read_buffer) |
| return ReportMismatch("file content != msg2", read_buffer, msg2); |
| int r = munmap(mapped, msg2.size()); |
| if (r < 0) |
| return ReportError("munmap for native FD returned error", errno); |
| } |
| |
| // Check mmap(2) for write with MAP_SHARED. |
| { |
| char* mapped = reinterpret_cast<char*>( |
| mmap(NULL, msg2.size(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); |
| if (mapped != MAP_FAILED) |
| return ReportError("mmap(w) for native FD must fail when opened readonly", |
| -1); |
| } |
| // END mmap(2) test with a file handle opened in READONLY mode. |
| |
| if (close(fd) < 0) |
| return ReportError("close for native FD returned error", errno); |
| #endif // !defined(PPAPI_OS_WIN) |
| |
| PASS(); |
| } |
| |
| std::string TestFileIO::MatchOpenExpectations(pp::FileSystem* file_system, |
| int32_t open_flags, |
| int32_t expectations) { |
| std::string bad_argument = |
| "TestFileIO::MatchOpenExpectations has invalid input arguments."; |
| bool invalid_combination = !!(expectations & INVALID_FLAG_COMBINATION); |
| if (invalid_combination) { |
| if (expectations != INVALID_FLAG_COMBINATION) |
| return bad_argument; |
| } else { |
| // Validate that one and only one of <some_expectation> and |
| // DONT_<some_expectation> is specified. |
| for (size_t remains = expectations, end = END_OF_OPEN_EXPECATION_PAIRS; |
| end != 0; remains >>= 2, end >>= 2) { |
| if (!!(remains & 1) == !!(remains & 2)) |
| return bad_argument; |
| } |
| } |
| bool create_if_doesnt_exist = !!(expectations & CREATE_IF_DOESNT_EXIST); |
| bool open_if_exists = !!(expectations & OPEN_IF_EXISTS); |
| bool truncate_if_exists = !!(expectations & TRUNCATE_IF_EXISTS); |
| |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); |
| pp::FileRef existent_file_ref( |
| *file_system, "/match_open_expectation_existent_non_empty_file"); |
| pp::FileRef nonexistent_file_ref( |
| *file_system, "/match_open_expectation_nonexistent_file"); |
| |
| // Setup files for test. |
| { |
| callback.WaitForResult(existent_file_ref.Delete(callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_TRUE(callback.result() == PP_OK || |
| callback.result() == PP_ERROR_FILENOTFOUND); |
| callback.WaitForResult(nonexistent_file_ref.Delete(callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_TRUE(callback.result() == PP_OK || |
| callback.result() == PP_ERROR_FILENOTFOUND); |
| |
| pp::FileIO existent_file_io(instance_); |
| callback.WaitForResult(existent_file_io.Open( |
| existent_file_ref, |
| PP_FILEOPENFLAG_CREATE | PP_FILEOPENFLAG_WRITE, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| int32_t rv = WriteEntireBuffer(instance_->pp_instance(), &existent_file_io, |
| 0, "foobar", callback_type()); |
| ASSERT_EQ(PP_OK, rv); |
| } |
| |
| pp::FileIO existent_file_io(instance_); |
| callback.WaitForResult(existent_file_io.Open(existent_file_ref, open_flags, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| if ((invalid_combination && callback.result() == PP_OK) || |
| (!invalid_combination && |
| ((callback.result() == PP_OK) != open_if_exists))) { |
| return ReportOpenError(open_flags); |
| } |
| |
| if (!invalid_combination && open_if_exists) { |
| PP_FileInfo info; |
| callback.WaitForResult(existent_file_io.Query(&info, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| ASSERT_EQ(PP_OK, callback.result()); |
| if (truncate_if_exists != (info.size == 0)) |
| return ReportOpenError(open_flags); |
| } |
| |
| pp::FileIO nonexistent_file_io(instance_); |
| callback.WaitForResult(nonexistent_file_io.Open(nonexistent_file_ref, |
| open_flags, |
| callback.GetCallback())); |
| CHECK_CALLBACK_BEHAVIOR(callback); |
| if ((invalid_combination && callback.result() == PP_OK) || |
| (!invalid_combination && |
| ((callback.result() == PP_OK) != create_if_doesnt_exist))) { |
| return ReportOpenError(open_flags); |
| } |
| |
| return std::string(); |
| } |
| |
| // TODO(viettrungluu): Test Close(). crbug.com/69457 |