blob: 3f6f829e40b984dd04016f7ee93890d2410d3338 [file] [log] [blame]
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -08001// Copyright 2021 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15#include <algorithm>
16#include <chrono>
17#include <filesystem>
18#include <fstream>
19#include <string>
20#include <thread>
21
22#include "gtest/gtest.h"
23#include "pw_assert/check.h"
24#include "pw_bytes/array.h"
25#include "pw_log/log.h"
26#include "pw_rpc/integration_testing.h"
27#include "pw_status/status.h"
28#include "pw_sync/binary_semaphore.h"
29#include "pw_thread_stl/options.h"
30#include "pw_transfer/client.h"
31#include "pw_transfer_test/test_server.raw_rpc.pb.h"
32
33namespace pw::transfer {
34namespace {
35
36using namespace std::chrono_literals;
37
Wyatt Hepler194c5e12021-12-01 10:53:12 -080038constexpr int kIterations = 5;
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -080039
40constexpr auto kData512 = bytes::Initialized<512>([](size_t i) { return i; });
41constexpr auto kData8192 = bytes::Initialized<8192>([](size_t i) { return i; });
Erik Gilling0aa0bef2022-03-10 09:49:26 -080042constexpr auto kDataHdlcEscape = bytes::Initialized<8192>(0x7e);
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -080043
44std::filesystem::path directory;
45
46// Reads the file that represents the transfer with the specific ID.
47std::string GetContent(uint32_t transfer_id) {
48 std::ifstream stream(directory / std::to_string(transfer_id),
49 std::ios::binary | std::ios::ate);
50 std::string contents(stream.tellg(), '\0');
51
52 stream.seekg(0, std::ios::beg);
53 PW_CHECK(stream.read(contents.data(), contents.size()));
54
55 return contents;
56}
57
58// Drops the null terminator from a string literal.
59template <size_t kLengthWithNull>
60ConstByteSpan AsByteSpan(const char (&data)[kLengthWithNull]) {
61 constexpr size_t kLength = kLengthWithNull - 1;
62 PW_CHECK_INT_EQ('\0', data[kLength], "Expecting null for last character");
63 return std::as_bytes(std::span(data, kLength));
64}
65
66constexpr ConstByteSpan AsByteSpan(ConstByteSpan data) { return data; }
67
Alexei Frolov22ee1142022-02-03 13:59:01 -080068thread::Options& TransferThreadOptions() {
69 static thread::stl::Options options;
70 return options;
71}
72
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -080073// Test fixture for pw_transfer tests. Clears the transfer files before and
74// after each test.
75class TransferIntegration : public ::testing::Test {
76 protected:
77 TransferIntegration()
Alexei Frolov22ee1142022-02-03 13:59:01 -080078 : transfer_thread_(chunk_buffer_, encode_buffer_),
79 system_thread_(TransferThreadOptions(), transfer_thread_),
80 client_(rpc::integration_test::client(),
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -080081 rpc::integration_test::kChannelId,
Alexei Frolov22ee1142022-02-03 13:59:01 -080082 transfer_thread_,
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -080083 256),
84 test_server_client_(rpc::integration_test::client(),
Alexei Frolov22ee1142022-02-03 13:59:01 -080085 rpc::integration_test::kChannelId) {
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -080086 ClearFiles();
87 }
88
89 ~TransferIntegration() {
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -080090 ClearFiles();
Alexei Frolov22ee1142022-02-03 13:59:01 -080091 transfer_thread_.Terminate();
92 system_thread_.join();
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -080093 }
94
95 // Sets the content of a transfer ID and returns a MemoryReader for that data.
96 template <typename T>
97 void SetContent(uint32_t transfer_id, const T& content) {
98 const ConstByteSpan data = AsByteSpan(content);
99 std::ofstream stream(directory / std::to_string(transfer_id),
100 std::ios::binary);
101 PW_CHECK(
102 stream.write(reinterpret_cast<const char*>(data.data()), data.size()));
103
104 sync::BinarySemaphore reload_complete;
105 rpc::RawUnaryReceiver call = test_server_client_.ReloadTransferFiles(
106 {}, [&reload_complete](ConstByteSpan, Status) {
107 reload_complete.release();
108 });
109 PW_CHECK(reload_complete.try_acquire_for(3s));
110 }
111
112 auto OnCompletion() {
113 return [this](Status status) {
114 last_status_ = status;
115 completed_.release();
116 };
117 }
118
119 // Checks that a read transfer succeeded and that the data matches the
120 // expected data.
121 void ExpectReadData(ConstByteSpan expected) {
122 ASSERT_EQ(WaitForCompletion(), OkStatus());
123 ASSERT_EQ(expected.size(), read_buffer_.size());
124
125 EXPECT_TRUE(std::equal(read_buffer_.begin(),
126 read_buffer_.end(),
127 std::as_bytes(std::span(expected)).begin()));
128 }
129
130 // Checks that a write transfer succeeded and that the written contents match.
131 void ExpectWriteData(uint32_t transfer_id, ConstByteSpan expected) {
132 ASSERT_EQ(WaitForCompletion(), OkStatus());
133
134 const std::string written = GetContent(transfer_id);
135 ASSERT_EQ(expected.size(), written.size());
136
137 ConstByteSpan bytes = std::as_bytes(std::span(written));
138 EXPECT_TRUE(std::equal(bytes.begin(), bytes.end(), expected.begin()));
139 }
140
141 // Waits for the transfer to complete and returns the status.
142 Status WaitForCompletion() {
143 PW_CHECK(completed_.try_acquire_for(3s));
144 return last_status_;
145 }
146
147 // Exact match the size of kData8192 to test filling the receiving buffer.
148 stream::MemoryWriterBuffer<kData8192.size()> read_buffer_;
149
150 Client& client() { return client_; }
151
152 private:
153 static void ClearFiles() {
154 for (const auto& entry : std::filesystem::directory_iterator(directory)) {
155 if (!entry.is_regular_file()) {
156 continue;
157 }
158
159 if (const std::string name = entry.path().filename().string();
160 std::all_of(name.begin(), name.end(), [](char c) {
161 return std::isdigit(c);
162 })) {
163 PW_LOG_DEBUG("Clearing transfer file %s", name.c_str());
164 std::filesystem::remove(entry.path());
165 }
166 }
167 }
168
Alexei Frolov22ee1142022-02-03 13:59:01 -0800169 std::byte chunk_buffer_[512];
170 std::byte encode_buffer_[512];
171 transfer::Thread<2, 2> transfer_thread_;
172 thread::Thread system_thread_;
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -0800173
174 Client client_;
175
176 pw_rpc::raw::TestServer::Client test_server_client_;
177 Status last_status_ = Status::Unknown();
178 sync::BinarySemaphore completed_;
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -0800179};
180
181TEST_F(TransferIntegration, Read_UnknownId) {
182 SetContent(123, "hello");
183
184 ASSERT_EQ(OkStatus(), client().Read(456, read_buffer_, OnCompletion()));
185
186 EXPECT_EQ(Status::NotFound(), WaitForCompletion());
187}
188
189#define PW_TRANSFER_TEST_READ(name, content) \
190 TEST_F(TransferIntegration, Read_##name) { \
191 for (int i = 0; i < kIterations; ++i) { \
192 const ConstByteSpan data = AsByteSpan(content); \
193 SetContent(__LINE__, data); \
194 ASSERT_EQ(OkStatus(), \
195 client().Read(__LINE__, read_buffer_, OnCompletion())); \
196 ExpectReadData(data); \
197 read_buffer_.clear(); \
198 } \
199 } \
200 static_assert(true, "Semicolons are required")
201
202PW_TRANSFER_TEST_READ(Empty, "");
203PW_TRANSFER_TEST_READ(SingleByte_1, "\0");
204PW_TRANSFER_TEST_READ(SingleByte_2, "?");
205PW_TRANSFER_TEST_READ(SmallData, "hunter2");
206PW_TRANSFER_TEST_READ(LargeData, kData512);
Wyatt Hepler194c5e12021-12-01 10:53:12 -0800207PW_TRANSFER_TEST_READ(VeryLargeData, kData8192);
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -0800208
209TEST_F(TransferIntegration, Write_UnknownId) {
210 constexpr std::byte kData[] = {std::byte{0}, std::byte{1}, std::byte{2}};
211 stream::MemoryReader reader(kData);
212
213 ASSERT_EQ(OkStatus(), client().Write(99, reader, OnCompletion()));
214 EXPECT_EQ(Status::NotFound(), WaitForCompletion());
215
216 SetContent(99, "something");
217 ASSERT_EQ(OkStatus(), client().Write(100, reader, OnCompletion()));
218 EXPECT_EQ(Status::NotFound(), WaitForCompletion());
219}
220
221#define PW_TRANSFER_TEST_WRITE(name, content) \
222 TEST_F(TransferIntegration, Write_##name) { \
223 for (int i = 0; i < kIterations; ++i) { \
224 SetContent(__LINE__, "This is junk data that should be overwritten!"); \
225 const ConstByteSpan data = AsByteSpan(content); \
226 stream::MemoryReader reader(data); \
227 ASSERT_EQ(OkStatus(), client().Write(__LINE__, reader, OnCompletion())); \
228 ExpectWriteData(__LINE__, data); \
229 } \
230 } \
231 static_assert(true, "Semicolons are required")
232
233PW_TRANSFER_TEST_WRITE(Empty, "");
234PW_TRANSFER_TEST_WRITE(SingleByte_1, "\0");
235PW_TRANSFER_TEST_WRITE(SingleByte_2, "?");
236PW_TRANSFER_TEST_WRITE(SmallData, "hunter2");
237PW_TRANSFER_TEST_WRITE(LargeData, kData512);
Erik Gilling0aa0bef2022-03-10 09:49:26 -0800238PW_TRANSFER_TEST_WRITE(HdlcEscape, kDataHdlcEscape);
Wyatt Hepler194c5e12021-12-01 10:53:12 -0800239PW_TRANSFER_TEST_WRITE(VeryLargeData, kData8192);
Wyatt Hepler6f6b6a12021-11-24 13:45:48 -0800240
241} // namespace
242} // namespace pw::transfer
243
244int main(int argc, char* argv[]) {
245 if (!pw::rpc::integration_test::InitializeClient(argc, argv, "PORT DIRECTORY")
246 .ok()) {
247 return 1;
248 }
249
250 if (argc != 3) {
251 PW_LOG_INFO("Usage: %s PORT DIRECTORY", argv[0]);
252 return 1;
253 }
254
255 pw::transfer::directory = argv[2];
256
257 return RUN_ALL_TESTS();
258}