@@ -20,6 +20,7 @@ TEST_CASE("InterfaceLog dump: full (req+rsp)", "[rpc][common][interface_log]") {
2020 .enabled = true ,
2121 .ifc_name = " eth_rpc" ,
2222 .container_folder = tmp_dir,
23+ .auto_flush = false ,
2324 .dump_response = true ,
2425 };
2526 auto ifc_log{std::make_unique<InterfaceLog>(settings)};
@@ -62,6 +63,7 @@ TEST_CASE("InterfaceLog dump: default (only req)", "[rpc][common][interface_log]
6263 .enabled = true ,
6364 .ifc_name = " eth_rpc" ,
6465 .container_folder = tmp_dir,
66+ .auto_flush = false ,
6567 };
6668 auto ifc_log{std::make_unique<InterfaceLog>(settings)};
6769 REQUIRE (!ifc_log->path ().empty ());
@@ -94,4 +96,191 @@ TEST_CASE("InterfaceLog dump: default (only req)", "[rpc][common][interface_log]
9496 CHECK (log_ifstream.eof ());
9597}
9698
99+ TEST_CASE (" InterfaceLog dump: two instances w/o auto-flush" , " [rpc][common][interface_log]" ) {
100+ const auto tmp_dir{TemporaryDirectory::get_unique_temporary_path ()};
101+ InterfaceLogSettings settings{
102+ .enabled = true ,
103+ .ifc_name = " eth_rpc" ,
104+ .container_folder = tmp_dir,
105+ .auto_flush = false ,
106+ };
107+ auto ifc_log1 = std::make_unique<InterfaceLog>(settings);
108+ auto ifc_log2 = std::make_unique<InterfaceLog>(settings);
109+ REQUIRE (ifc_log1->path () == ifc_log2->path ());
110+
111+ std::ifstream log_ifstream{ifc_log1->path ().string ()};
112+
113+ static constexpr size_t kLogBufferSize {1024 * 4 };
114+ static const size_t kLogLineHeaderSize {InterfaceLog::kLogLineHeaderSize };
115+
116+ std::string request1;
117+ std::string request2 (100 , ' B' ); // always less than page size
118+
119+ SECTION (" less than page size" ) {
120+ request1.assign (kLogBufferSize - kLogLineHeaderSize - 1 /* \n*/ - 1 , ' A' );
121+
122+ SECTION (" same instance" ) {
123+ // Logging request1 is NOT sufficient to trigger page write w/o flush
124+ ifc_log1->log_req (request1);
125+ CHECK (std::filesystem::file_size (ifc_log1->path ()) == 0 );
126+
127+ // Logging request2 crosses the page size, so one page is written w/o flush
128+ ifc_log1->log_req (request2);
129+ CHECK (std::filesystem::file_size (ifc_log1->path ()) == kLogBufferSize );
130+
131+ // Flushing writes the whole write buffer content
132+ ifc_log1->flush ();
133+
134+ // Log file content is exactly log_line_header + request1 + log_line_header + request2
135+ std::string content;
136+ std::getline (log_ifstream, content);
137+ CHECK (content.substr (kLogLineHeaderSize ) == request1);
138+ std::getline (log_ifstream, content);
139+ CHECK (content.substr (kLogLineHeaderSize ) == request2);
140+ }
141+ SECTION (" different instances" ) {
142+ // Logging request1 through ifc_log1 is NOT sufficient to trigger page write w/o flush
143+ ifc_log1->log_req (request1);
144+ CHECK (std::filesystem::file_size (ifc_log1->path ()) == 0 );
145+
146+ // Logging request2 through ifc_log2 DOES NOT trigger page write: write buffers are separate
147+ ifc_log2->log_req (request2);
148+ CHECK (std::filesystem::file_size (ifc_log2->path ()) == 0 );
149+
150+ // Flushing both instances dumps the separate write buffers in order
151+ ifc_log2->flush ();
152+ ifc_log1->flush ();
153+
154+ // Log file content is exactly log_line_header + request2 + log_line_header + request1
155+ std::string content;
156+ std::getline (log_ifstream, content);
157+ CHECK (content.substr (kLogLineHeaderSize ) == request2);
158+ std::getline (log_ifstream, content);
159+ CHECK (content.substr (kLogLineHeaderSize ) == request1);
160+ }
161+ // No other content is present
162+ CHECK (log_ifstream.get () == -1 );
163+ CHECK (log_ifstream.eof ());
164+
165+ CHECK (std::filesystem::file_size (ifc_log1->path ()) ==
166+ (request1.size () + kLogLineHeaderSize + 1 ) +
167+ (request2.size () + kLogLineHeaderSize + 1 ));
168+ }
169+
170+ SECTION (" greater than or equal to page size" ) {
171+ request1.assign (4096 - kLogLineHeaderSize + 1 , ' A' );
172+
173+ SECTION (" same instance" ) {
174+ // Logging request1 is sufficient to trigger page write w/o flush
175+ ifc_log1->log_req (request1);
176+ CHECK (std::filesystem::file_size (ifc_log1->path ()) == kLogBufferSize );
177+
178+ // Logging request2 DOES NOT trigger another page write
179+ ifc_log1->log_req (request2);
180+ CHECK (std::filesystem::file_size (ifc_log1->path ()) == kLogBufferSize );
181+
182+ // Flushing writes the whole write buffer content
183+ ifc_log1->flush ();
184+
185+ // Log file content is exactly log_line_header + request1 + log_line_header + request2
186+ std::string content;
187+ std::getline (log_ifstream, content);
188+ CHECK (content.substr (kLogLineHeaderSize ) == request1);
189+ std::getline (log_ifstream, content);
190+ CHECK (content.substr (kLogLineHeaderSize ) == request2);
191+ }
192+ SECTION (" different instances w/o flush: possible truncation" ) {
193+ // Logging request1 through ifc_log1 is sufficient to trigger page write w/o flush
194+ ifc_log1->log_req (request1);
195+ CHECK (std::filesystem::file_size (ifc_log1->path ()) == kLogBufferSize );
196+
197+ // Logging request2 through ifc_log2 DOES NOT trigger another page write
198+ ifc_log2->log_req (request2);
199+ CHECK (std::filesystem::file_size (ifc_log2->path ()) == kLogBufferSize );
200+
201+ // Flushing ifc_log2 BEFORE ifc_log1 generates a mixed content: truncated request1 + request2
202+ ifc_log2->flush ();
203+
204+ // Log file content is exactly log_line_header + request1 TRUNCATED AT 4k + log_line_header + request2
205+ std::string content;
206+ std::getline (log_ifstream, content);
207+ CHECK (content.substr (0 , kLogLineHeaderSize ).ends_with (" REQ -> " ));
208+ CHECK (content.substr (kLogLineHeaderSize , kLogBufferSize - kLogLineHeaderSize ) == std::string (kLogBufferSize - kLogLineHeaderSize , ' A' ));
209+ CHECK (content.substr (kLogBufferSize , kLogLineHeaderSize ).ends_with (" REQ -> " ));
210+ CHECK (content.substr (kLogBufferSize + kLogLineHeaderSize ) == request2);
211+ }
212+ SECTION (" different instances w/ flush: no truncation" ) {
213+ // Logging request1 through ifc_log1 and flushing
214+ ifc_log1->log_req (request1);
215+ ifc_log1->flush ();
216+ CHECK (std::filesystem::file_size (ifc_log1->path ()) == request1.size () + kLogLineHeaderSize + 1 );
217+
218+ // Logging request2 through ifc_log2 and flushing
219+ ifc_log2->log_req (request2);
220+ ifc_log2->flush ();
221+ CHECK (std::filesystem::file_size (ifc_log2->path ()) ==
222+ (request1.size () + kLogLineHeaderSize + 1 ) +
223+ (request2.size () + kLogLineHeaderSize + 1 ));
224+
225+ // Log file content is exactly log_line_header + request2 + log_line_header + request1
226+ std::string content;
227+ std::getline (log_ifstream, content);
228+ CHECK (content.substr (kLogLineHeaderSize ) == request1);
229+ std::getline (log_ifstream, content);
230+ CHECK (content.substr (kLogLineHeaderSize ) == request2);
231+ }
232+ // No other content is present
233+ CHECK (log_ifstream.get () == -1 );
234+ CHECK (log_ifstream.eof ());
235+ }
236+ }
237+
238+ TEST_CASE (" InterfaceLog dump: two instances w/ auto-flush" , " [rpc][common][interface_log]" ) {
239+ const auto tmp_dir{TemporaryDirectory::get_unique_temporary_path ()};
240+ InterfaceLogSettings settings{
241+ .enabled = true ,
242+ .ifc_name = " eth_rpc" ,
243+ .container_folder = tmp_dir,
244+ };
245+ auto ifc_log1 = std::make_unique<InterfaceLog>(settings);
246+ auto ifc_log2 = std::make_unique<InterfaceLog>(settings);
247+ REQUIRE (ifc_log1->path () == ifc_log2->path ());
248+
249+ static constexpr size_t kLogBufferSize {1024 * 4 };
250+ static const size_t kLogLineHeaderSize {InterfaceLog::kLogLineHeaderSize };
251+
252+ std::string request1;
253+ std::string request2 (100 , ' B' ); // always less than page size
254+
255+ SECTION (" less than page size" ) {
256+ request1.assign (kLogBufferSize - kLogLineHeaderSize - 1 /* \n*/ - 1 , ' A' );
257+ }
258+
259+ SECTION (" greater than or equal to page size" ) {
260+ request1.assign (4096 - kLogLineHeaderSize + 1 , ' A' );
261+ }
262+
263+ // Logging request1 through ifc_log1 implicitly flushes
264+ ifc_log1->log_req (request1);
265+ CHECK (std::filesystem::file_size (ifc_log1->path ()) == request1.size () + kLogLineHeaderSize + 1 );
266+
267+ // Logging request2 through ifc_log2 implicitly flushes
268+ ifc_log2->log_req (request2);
269+ CHECK (std::filesystem::file_size (ifc_log2->path ()) ==
270+ (request1.size () + kLogLineHeaderSize + 1 ) +
271+ (request2.size () + kLogLineHeaderSize + 1 ));
272+
273+ // Log file content is exactly log_line_header + request1 + log_line_header + request2
274+ std::ifstream log_ifstream{ifc_log1->path ().string ()};
275+ std::string content;
276+ std::getline (log_ifstream, content);
277+ CHECK (content.substr (kLogLineHeaderSize ) == request1);
278+ std::getline (log_ifstream, content);
279+ CHECK (content.substr (kLogLineHeaderSize ) == request2);
280+
281+ // No other content is present
282+ CHECK (log_ifstream.get () == -1 );
283+ CHECK (log_ifstream.eof ());
284+ }
285+
97286} // namespace silkworm::rpc
0 commit comments