Skip to content

Commit

Permalink
[libc][NFC] Extend ErrnoSetterMatcher to test expected inequalities. (#…
Browse files Browse the repository at this point in the history
…67153)

Before this change, ErrnoSetterMatcher only allowed testing for equality
of the expected return and errno values. This change extends it to allow
testing for expected inequalities of the return and errno values. The
test libc.test.src.stdio.fileop_test has been updated to use the
ErrnoSetterMatcher with tests for inequalities.
  • Loading branch information
sivachandra authored Sep 22, 2023
1 parent 3510552 commit 62a3d84
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 42 deletions.
117 changes: 94 additions & 23 deletions libc/test/UnitTest/ErrnoSetterMatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,42 @@ namespace testing {

namespace internal {

enum class CompareAction { EQ = 0, GE, GT, LE, LT, NE };

constexpr const char *CompareMessage[] = {
"equal to", "greater than or equal to",
"greater than", "less than or equal to",
"less than", "not equal to"};

template <typename T> struct Comparator {
CompareAction cmp;
T expected;
bool compare(T actual) {
switch (cmp) {
case CompareAction::EQ:
return actual == expected;
case CompareAction::NE:
return actual != expected;
case CompareAction::GE:
return actual >= expected;
case CompareAction::GT:
return actual > expected;
case CompareAction::LE:
return actual <= expected;
case CompareAction::LT:
return actual < expected;
}
__builtin_unreachable();
}

const char *str() { return CompareMessage[static_cast<int>(cmp)]; }
};

template <typename T> class ErrnoSetterMatcher : public Matcher<T> {
T ExpectedReturn;
T ActualReturn;
int ExpectedErrno;
int ActualErrno;
Comparator<T> return_cmp;
Comparator<int> errno_cmp;
T actual_return;
int actual_errno;

// Even though this is a errno matcher primarily, it has to cater to platforms
// which do not have an errno. This predicate checks if errno matching is to
Expand All @@ -39,55 +70,95 @@ template <typename T> class ErrnoSetterMatcher : public Matcher<T> {
}

public:
ErrnoSetterMatcher(T ExpectedReturn, int ExpectedErrno)
: ExpectedReturn(ExpectedReturn), ExpectedErrno(ExpectedErrno) {}
ErrnoSetterMatcher(Comparator<T> rcmp) : return_cmp(rcmp) {}
ErrnoSetterMatcher(Comparator<T> rcmp, Comparator<int> ecmp)
: return_cmp(rcmp), errno_cmp(ecmp) {}

ErrnoSetterMatcher<T> with_errno(Comparator<int> ecmp) {
errno_cmp = ecmp;
return *this;
}

void explainError() override {
if (ActualReturn != ExpectedReturn) {
if (!return_cmp.compare(actual_return)) {
if constexpr (cpp::is_floating_point_v<T>) {
tlog << "Expected return value to be: "
<< str(fputil::FPBits<T>(ExpectedReturn)) << '\n';
tlog << " But got: "
<< str(fputil::FPBits<T>(ActualReturn)) << '\n';
tlog << "Expected return value to be " << return_cmp.str() << ": "
<< str(fputil::FPBits<T>(return_cmp.expected)) << '\n'
<< " But got: "
<< str(fputil::FPBits<T>(actual_return)) << '\n';
} else {
tlog << "Expected return value to be " << ExpectedReturn << " but got "
<< ActualReturn << ".\n";
tlog << "Expected return value to be " << return_cmp.str() << " "
<< return_cmp.expected << " but got " << actual_return << ".\n";
}
}

if constexpr (!ignore_errno()) {
if (ActualErrno != ExpectedErrno) {
tlog << "Expected errno to be \"" << get_error_string(ExpectedErrno)
<< "\" but got \"" << get_error_string(ActualErrno) << "\".\n";
if (!errno_cmp.compare(actual_errno)) {
tlog << "Expected errno to be " << errno_cmp.str() << " \""
<< get_error_string(errno_cmp.expected) << "\" but got \""
<< get_error_string(actual_errno) << "\".\n";
}
}
}

bool match(T Got) {
ActualReturn = Got;
ActualErrno = libc_errno;
bool match(T got) {
actual_return = got;
actual_errno = libc_errno;
libc_errno = 0;
if constexpr (ignore_errno())
return Got == ExpectedReturn;
return return_cmp.compare(actual_return);
else
return Got == ExpectedReturn && ActualErrno == ExpectedErrno;
return return_cmp.compare(actual_return) &&
errno_cmp.compare(actual_errno);
}
};

} // namespace internal

namespace ErrnoSetterMatcher {

template <typename T> internal::Comparator<T> LT(T val) {
return internal::Comparator<T>{internal::CompareAction::LT, val};
}

template <typename T> internal::Comparator<T> LE(T val) {
return internal::Comparator<T>{internal::CompareAction::LE, val};
}

template <typename T> internal::Comparator<T> GT(T val) {
return internal::Comparator<T>{internal::CompareAction::GT, val};
}

template <typename T> internal::Comparator<T> GE(T val) {
return internal::Comparator<T>{internal::CompareAction::GE, val};
}

template <typename T> internal::Comparator<T> EQ(T val) {
return internal::Comparator<T>{internal::CompareAction::EQ, val};
}

template <typename T> internal::Comparator<T> NE(T val) {
return internal::Comparator<T>{internal::CompareAction::NE, val};
}

template <typename RetT = int>
static internal::ErrnoSetterMatcher<RetT> Succeeds(RetT ExpectedReturn = 0,
int ExpectedErrno = 0) {
return {ExpectedReturn, ExpectedErrno};
return internal::ErrnoSetterMatcher<RetT>(EQ(ExpectedReturn),
EQ(ExpectedErrno));
}

template <typename RetT = int>
static internal::ErrnoSetterMatcher<RetT> Fails(int ExpectedErrno,
RetT ExpectedReturn = -1) {
return {ExpectedReturn, ExpectedErrno};
return internal::ErrnoSetterMatcher<RetT>(EQ(ExpectedReturn),
EQ(ExpectedErrno));
}

template <typename RetT>
static internal::ErrnoSetterMatcher<RetT>
returns(internal::Comparator<RetT> cmp) {
return internal::ErrnoSetterMatcher<RetT>(cmp);
}

} // namespace ErrnoSetterMatcher
Expand Down
43 changes: 24 additions & 19 deletions libc/test/src/stdio/fileop_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@
#include "src/stdio/fread.h"
#include "src/stdio/fseek.h"
#include "src/stdio/fwrite.h"
#include "test/UnitTest/ErrnoSetterMatcher.h"
#include "test/UnitTest/Test.h"

#include "src/errno/libc_errno.h"
#include <stdio.h>

using __llvm_libc::testing::ErrnoSetterMatcher::EQ;
using __llvm_libc::testing::ErrnoSetterMatcher::NE;
using __llvm_libc::testing::ErrnoSetterMatcher::returns;

TEST(LlvmLibcFILETest, SimpleFileOperations) {
constexpr char FILENAME[] = "testdata/simple_operations.test";
::FILE *file = __llvm_libc::fopen(FILENAME, "w");
Expand All @@ -31,9 +36,9 @@ TEST(LlvmLibcFILETest, SimpleFileOperations) {

// This is not a readable file.
char read_data[sizeof(CONTENT)];
ASSERT_EQ(__llvm_libc::fread(read_data, 1, sizeof(CONTENT), file), size_t(0));
ASSERT_THAT(__llvm_libc::fread(read_data, 1, sizeof(CONTENT), file),
returns(EQ(size_t(0))).with_errno(NE(0)));
ASSERT_NE(__llvm_libc::ferror(file), 0);
EXPECT_NE(libc_errno, 0);
libc_errno = 0;

__llvm_libc::clearerr(file);
Expand Down Expand Up @@ -62,25 +67,25 @@ TEST(LlvmLibcFILETest, SimpleFileOperations) {
ASSERT_NE(__llvm_libc::feof(file), 0);

// Should be an error to write.
ASSERT_EQ(size_t(0), __llvm_libc::fwrite(CONTENT, 1, sizeof(CONTENT), file));
ASSERT_THAT(__llvm_libc::fwrite(CONTENT, 1, sizeof(CONTENT), file),
returns(EQ(size_t(0))).with_errno(NE(0)));
ASSERT_NE(__llvm_libc::ferror(file), 0);
ASSERT_NE(libc_errno, 0);
libc_errno = 0;

__llvm_libc::clearerr(file);

// Should be an error to puts.
ASSERT_EQ(EOF, __llvm_libc::fputs(CONTENT, file));
ASSERT_THAT(__llvm_libc::fputs(CONTENT, file),
returns(EQ(EOF)).with_errno(NE(0)));
ASSERT_NE(__llvm_libc::ferror(file), 0);
ASSERT_NE(libc_errno, 0);
libc_errno = 0;

__llvm_libc::clearerr(file);
ASSERT_EQ(__llvm_libc::ferror(file), 0);

libc_errno = 0;
ASSERT_EQ(__llvm_libc::fwrite("nothing", 1, 1, file), size_t(0));
ASSERT_NE(libc_errno, 0);
ASSERT_THAT(__llvm_libc::fwrite("nothing", 1, 1, file),
returns(EQ(0)).with_errno(NE(0)));
libc_errno = 0;

ASSERT_EQ(__llvm_libc::fclose(file), 0);
Expand All @@ -97,8 +102,8 @@ TEST(LlvmLibcFILETest, SimpleFileOperations) {

// This is not a readable file.
libc_errno = 0;
ASSERT_EQ(__llvm_libc::fread(data, 1, 1, file), size_t(0));
ASSERT_NE(libc_errno, 0);
ASSERT_THAT(__llvm_libc::fread(data, 1, 1, file),
returns(EQ(0)).with_errno(NE(0)));
libc_errno = 0;

ASSERT_EQ(0, __llvm_libc::fclose(file));
Expand Down Expand Up @@ -162,22 +167,22 @@ TEST(LlvmLibcFILETest, FOpenFWriteSizeGreaterThanOne) {
FILE *file = __llvm_libc::fopen(FILENAME, "w");
ASSERT_FALSE(file == nullptr);
ASSERT_EQ(size_t(0), __llvm_libc::fwrite(WRITE_DATA, 0, 1, file));
ASSERT_EQ(WRITE_NMEMB, __llvm_libc::fwrite(WRITE_DATA, sizeof(MyStruct),
WRITE_NMEMB, file));
EXPECT_EQ(libc_errno, 0);
ASSERT_THAT(
__llvm_libc::fwrite(WRITE_DATA, sizeof(MyStruct), WRITE_NMEMB, file),
returns(EQ(WRITE_NMEMB)).with_errno(EQ(0)));
ASSERT_EQ(__llvm_libc::fclose(file), 0);

file = __llvm_libc::fopen(FILENAME, "r");
ASSERT_FALSE(file == nullptr);
MyStruct read_data[WRITE_NMEMB];
ASSERT_EQ(size_t(0), __llvm_libc::fread(read_data, 0, 1, file));
ASSERT_EQ(WRITE_NMEMB,
__llvm_libc::fread(read_data, sizeof(MyStruct), WRITE_NMEMB, file));
EXPECT_EQ(libc_errno, 0);
ASSERT_THAT(
__llvm_libc::fread(read_data, sizeof(MyStruct), WRITE_NMEMB, file),
returns(EQ(WRITE_NMEMB)).with_errno(EQ(0)));
// Trying to read more should fetch nothing.
ASSERT_EQ(size_t(0),
__llvm_libc::fread(read_data, sizeof(MyStruct), WRITE_NMEMB, file));
EXPECT_EQ(libc_errno, 0);
ASSERT_THAT(
__llvm_libc::fread(read_data, sizeof(MyStruct), WRITE_NMEMB, file),
returns(EQ(0)).with_errno(EQ(0)));
EXPECT_NE(__llvm_libc::feof(file), 0);
EXPECT_EQ(__llvm_libc::ferror(file), 0);
ASSERT_EQ(__llvm_libc::fclose(file), 0);
Expand Down

0 comments on commit 62a3d84

Please sign in to comment.