/* * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #include "precompiled.hpp" #include "jvm.h" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" #include "runtime/os.hpp" #include "utilities/istream.hpp" #include "unittest.hpp" template class BlockInputStream : public inputStream { BlockClass _input; public: template BlockInputStream(Arg... arg) : _input(arg...) { set_input(&_input); } }; #define EXPECT_MEMEQ(s1, s2, len) \ EXPECT_PRED_FORMAT3(CmpHelperMEMEQ, s1, s2, len) // cf. ::testing::internal::CmpHelperSTREQ testing::AssertionResult CmpHelperMEMEQ(const char* s1_expression, const char* s2_expression, const char* len_expression, const char* s1, const char* s2, size_t len) { if (s1 == nullptr || s2 == nullptr) { return testing::internal::CmpHelperEQ(s1_expression, s2_expression, s1, s2); } int c = ::memcmp(s1, s2, len); if (c == 0) { return testing::AssertionSuccess(); } ::std::string str1, str2; for (auto which = 0; which <= 1; which++) { auto s = which ? s1 : s2; auto &str = which ? str1 : str2; std::stringstream buf; buf << "{"; for (size_t i = 0; i < len; i++) { char c = s[i]; switch (c) { case '\0': buf << "\\0"; break; case '\n': buf << "\\n"; break; case '\\': buf << "\\\\"; break; default: buf << c; break; } } buf << "}[" << len_expression << "=" << len << "]"; str = buf.str(); } return testing::internal::CmpHelperSTREQ(s1_expression, s2_expression, str1.c_str(), str2.c_str()); } static int firstdiff(char* b1, char* b2, int blen) { for (int i = 0; i < blen; i++) { if (b1[i] != b2[i]) return i; } return -1; } static char* get_temp_file(bool VERBOSE, const char* filename) { const char* tmp_dir = os::get_temp_directory(); const char* file_sep = os::file_separator(); size_t temp_file_len = strlen(tmp_dir) + strlen(file_sep) + strlen(filename) + 28; char* temp_file = NEW_C_HEAP_ARRAY(char, temp_file_len, mtInternal); int ret = jio_snprintf(temp_file, temp_file_len, "%s%spid%d.%s", tmp_dir, file_sep, os::current_process_id(), filename); if (VERBOSE) tty->print_cr("temp_file = %s", temp_file); return temp_file; } static const char* get_temp_file(bool VERBOSE) { static const char* temp_file = get_temp_file(VERBOSE, "test_istream"); return temp_file; } #define EIGHTY 80 #define LC0(x) ('/' + (((unsigned)(x)+1) % EIGHTY)) #define LC(line,col) LC0((col) * (line)) #define COLS 30 static int cases, total, zeroes; #ifdef ASSERT #define istream_coverage_mode(mode, a,b,c) \ inputStream::coverage_mode(mode, a,b,c) #else #define istream_coverage_mode(mode, a,b,c) #endif // Fill in a test pattern of ascii characters. // Each line is ncols long, plus a line termination of lelen (1 or 2). // Each character is a fixed, static function of the line and column. // This enables test logic to predict exactly what will be read in each line. static void fill_pattern(bool VERBOSE, char* pat, int patlen, int ncols, int lelen, int& full_lines, int& partial_line, const char* &line_end, const char* &partial_line_end) { full_lines = partial_line = 0; for (int i = 0; i < patlen; i++) { int line = (i / (ncols+lelen)) + 1; // 1-based line number int col = (i % (ncols+lelen)) + 1; // 1-based column number if (col <= ncols) { pat[i] = LC(line, col); partial_line = 1; } else if (col < ncols+lelen) { pat[i] = i == patlen - 1 ? '!' : '%'; partial_line = 1; } else { assert(col == ncols+lelen, ""); pat[i] = '!'; full_lines++; partial_line = 0; } } pat[patlen] = '\0'; if (VERBOSE) tty->print_cr("PATTERN=%d+%d[%s]", full_lines, partial_line, pat); for (int i = 0; i < patlen; i++) { assert(pat[i] != '%' || (i+1 < patlen && pat[i+1] == '!'), ""); if (pat[i] == '!') pat[i] = '\n'; if (pat[i] == '%') pat[i] = '\r'; } assert(pat[patlen-1] != '\r', ""); line_end = (lelen == 2 ? "\r\n" : "\n"); int partial_line_bytes = patlen - (full_lines * (ncols + lelen)); assert(partial_line_bytes < ncols + lelen, ""); partial_line_end = (partial_line_bytes == ncols + 1) ? "\n" : ""; } static const int MAX_PATLEN = COLS * (COLS-1); static void istream_test_driver(const bool VERBOSE, const int patlen, const int ncols, const int lelen, const bool TEST_SET_POSITION, const bool TEST_PUSH_BACK, const bool TEST_EXPAND_REDUCE) { DEBUG_ONLY( istream_coverage_mode(VERBOSE ? 2 : 1, cases, total, zeroes) ); const char* temp_file = get_temp_file(VERBOSE); unlink(temp_file); char pat[MAX_PATLEN+1]; int full_lines = 0, partial_line = 0; const char* line_end = "\n"; const char* partial_line_end = ""; fill_pattern(VERBOSE, pat, patlen, ncols, lelen, full_lines, partial_line, line_end, partial_line_end); char pat2[sizeof(pat)]; // copy of pat to help detect scribbling memcpy(pat2, pat, sizeof(pat)); // Make three kinds of stream and test them all. MemoryInput _min(pat2, patlen); inputStream sin(&_min); if (VERBOSE) { tty->print("at %llx ", (unsigned long long)(intptr_t)&sin); sin.dump("sin"); } { fileStream tfs(temp_file); guarantee(tfs.is_open(), "cannot open temp file"); tfs.write(pat, patlen); } BlockInputStream fin(temp_file); if (VERBOSE) { tty->print("at %llx ", (unsigned long long)(intptr_t)&fin); fin.dump("fin"); } BlockInputStream min(&pat2[0], patlen); if (VERBOSE) { tty->print("at %llx ", (unsigned long long)(intptr_t)&min); sin.dump("min"); } inputStream* ins[] = { &sin, &fin, &min }; const char* in_names[] = { "sin", "fin", "min" }; const char* test_mode = (TEST_SET_POSITION ? (!TEST_PUSH_BACK ? "(seek)" : "(seek/push)") : TEST_EXPAND_REDUCE ? (!TEST_PUSH_BACK ? "(exp/red)" : "(exp/red/push)") : (!TEST_PUSH_BACK ? "(plain)" : "(push)")); for (int which = 0; which < 3; which++) { inputStream& in = *ins[which]; const char* in_name = in_names[which]; int lineno; char* lp = (char*)"--"; #define LPEQ \ in_name << test_mode \ << " ncols=" << ncols << " lelen=" << lelen \ << " full=" << full_lines << " lineno=" << lineno \ << " [" << lp << "]" << (in.dump("expect"), "") if (VERBOSE) tty->print_cr("testing %s%s patlen=%d ncols=%d full_lines=%d partial_line=%d", in_name, test_mode, patlen, ncols, full_lines, partial_line); int pos_to_set = 0, line_to_set = 1; // for TEST_SET_POSITION only for (int phase = 0; phase <= (TEST_SET_POSITION ? 1 : 0); phase++) { lineno = 1; for (; lineno <= full_lines + partial_line; lineno++) { EXPECT_EQ(-1, firstdiff(pat, pat2, patlen + 1)); if (VERBOSE) in.dump("!done?"); bool done = in.done(); EXPECT_TRUE(!done) < full_lines) expect_len = MIN2(ncols, patlen % (ncols+lelen)); EXPECT_EQ(len, expect_len) < SHORT_NCOLS) break; } else if (ncols > COLS && ncols < patlen - COLS) { ncols += ncols / 7; if (ncols > patlen - COLS) ncols = (patlen - COLS); } for (int lelen = 1; lelen <= 2; lelen++) { // try both kinds of newline istream_test_driver(VERBOSE, patlen, ncols, lelen, TEST_SET_POSITION, TEST_PUSH_BACK, TEST_EXPAND_REDUCE); } } } TEST_VM(istream, basic) { const bool VERBOSE = false; istream_test_driver(VERBOSE, false, false, false); } TEST_VM(istream, coverage) { const bool VERBOSE = false; #ifdef ASSERT istream_coverage_mode(0, cases, total, zeroes); if (cases == 0) return; if (VERBOSE || zeroes != 0) istream_coverage_mode(-1, cases, total, zeroes); EXPECT_EQ(zeroes, 0) << "zeroes: " << zeroes << "/" << cases; #endif //ASSERT }