diff --git a/docs/shadertrap_language.md b/docs/shadertrap_language.md index 90235bc..d8f0df2 100644 --- a/docs/shadertrap_language.md +++ b/docs/shadertrap_language.md @@ -54,11 +54,31 @@ A brief description of each ShaderTrap command now follows. The commands are pre This command has two forms. ``` -ASSERT_EQUAL BUFFERS buffer_1 buffer_2 +ASSERT_EQUAL BUFFERS buffer_1 buffer_2 [FORMAT format_entry_1 ... format_entry_n] ``` Checks whether `buffer_1` and `buffer_2`, which must be buffers produced by `CREATE_BUFFER`, contain equal contents. Performs a byte-level comparison of the buffers. Yields an error if the buffers have different sizes, or if they have the same size but differ at any single byte. +- `buffer_1` and `buffer_2` are the buffers to be compared +- Each `format_entry_i` is either: + - `SKIP_BYTES count` for some positive integer `count` that must be a multiple of 4 + - `type count` for some positive integer `count`, where `type` is one of `byte`, `int`, `uint` or `float`, and where `count` is a multiple of 4 if `type` is `byte` + +`FORMAT` is an optional parameter that specifies the format of the data that will be compared. If `FORMAT` is specified, it should be followed by at least 1 `format_entry_i` component. The data is then formatted by processing the `format_entry_i` components as follows, with respect to `offset`, a byte offset into the buffer, initialised to 0: + +- If `format_entry_i` is `SKIP_BYTES count` then `offset` is incremented by `count`. This allows padding or irrelevant data to be ignored. + +- If `format_entry_i` is byte count then count successive bytes from the buffers are compared, starting from position offset, with any byte-level differences reported, after which offset is incremented by `count`. + +- If `format_entry_i` is int count then count successive 4-byte sequences from the buffers are compared, starting from position offset, after which offset is incremented by 4* `count`. Any mismatches between 4-byte sequences are reported as differences between 32-bit signed integers. + +- If format_entry_i is `uint count` then the effect is the same as for the `int` case, except that mismatches are reported as differences between 32-bit unsigned integers. + +- If `format_entry_i` is `float count` then the effect is the same as for the `int` case, except that mismatches are reported as differences between 32-bit floating-point values. Note that this does not involve comparing floating-point numbers: comparisons are made at the byte level. + +The sum of `count` for all `byte` and `SKIP_BYTES` entries, plus the sum of 4*`count` for all `int`, `uint` and `float` entries, must equal the buffer size in bytes - i.e., every byte in the buffer must be accounted for. + +If `FORMAT` is not explicitly specified in the command we assumed it to be `FORMAT byte n` where `n` is the size of `buffer_1` and `buffer_2`. ``` ASSERT_EQUAL RENDERBUFFERS renderbuffer_1 renderbuffer_2 ``` diff --git a/examples/OpenGL45/assert_equal_examples/assert_equal1.shadertrap b/examples/OpenGL45/assert_equal_examples/assert_equal1.shadertrap new file mode 100644 index 0000000..34bea44 --- /dev/null +++ b/examples/OpenGL45/assert_equal_examples/assert_equal1.shadertrap @@ -0,0 +1,23 @@ +# Copyright 2021 The ShaderTrap Project Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the Licens + +GL 4.5 + +# Valid example + +CREATE_BUFFER buf1 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 + +CREATE_BUFFER buf2 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 + +ASSERT_EQUAL BUFFERS buf1 buf2 FORMAT float 4 diff --git a/examples/OpenGL45/assert_equal_examples/assert_equal2.shadertrap b/examples/OpenGL45/assert_equal_examples/assert_equal2.shadertrap new file mode 100644 index 0000000..7b580a9 --- /dev/null +++ b/examples/OpenGL45/assert_equal_examples/assert_equal2.shadertrap @@ -0,0 +1,23 @@ +# Copyright 2021 The ShaderTrap Project Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +GL 4.5 + +# Skips the first 2 unequal values. + +CREATE_BUFFER buf1 SIZE_BYTES 16 INIT_VALUES float 5.0 6.0 3.0 4.0 + +CREATE_BUFFER buf2 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 + +ASSERT_EQUAL BUFFERS buf1 buf2 FORMAT SKIP_BYTES 8 float 2 diff --git a/examples/OpenGL45/assert_equal_examples/assert_equal3.shadertrap b/examples/OpenGL45/assert_equal_examples/assert_equal3.shadertrap new file mode 100644 index 0000000..9fcb7ff --- /dev/null +++ b/examples/OpenGL45/assert_equal_examples/assert_equal3.shadertrap @@ -0,0 +1,23 @@ +# Copyright 2021 The ShaderTrap Project Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +GL 4.5 + +# Skips unequal values in between equal ones. + +CREATE_BUFFER buf1 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 5.0 4.0 + +CREATE_BUFFER buf2 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 + +ASSERT_EQUAL BUFFERS buf1 buf2 FORMAT float 2 SKIP_BYTES 4 float 1 diff --git a/src/libshadertrap/include/libshadertrap/command_assert_equal.h b/src/libshadertrap/include/libshadertrap/command_assert_equal.h index 992b3ec..5c965b6 100644 --- a/src/libshadertrap/include/libshadertrap/command_assert_equal.h +++ b/src/libshadertrap/include/libshadertrap/command_assert_equal.h @@ -15,8 +15,10 @@ #ifndef LIBSHADERTRAP_COMMAND_ASSERT_EQUAL_H #define LIBSHADERTRAP_COMMAND_ASSERT_EQUAL_H +#include #include #include +#include #include "libshadertrap/command.h" #include "libshadertrap/token.h" @@ -25,8 +27,20 @@ namespace shadertrap { class CommandAssertEqual : public Command { public: + struct FormatEntry { + enum class Kind { kByte, kFloat, kInt, kUint, kSkip }; + std::unique_ptr token; + Kind kind; + size_t count; + }; + // Constructor used for an assertion about the equality of two buffers. + CommandAssertEqual(std::unique_ptr start_token, + std::unique_ptr argument_identifier_1, + std::unique_ptr argument_identifier_2, + std::vector format_entries); + + // Constructor used for an assertion about the equality of two renderbuffers. CommandAssertEqual(std::unique_ptr start_token, - bool arguments_are_renderbuffers, std::unique_ptr argument_identifier_1, std::unique_ptr argument_identifier_2); @@ -52,11 +66,14 @@ class CommandAssertEqual : public Command { return *argument_identifier_2_; } + std::vector& GetFormatEntries() { return format_entries_; } + private: // true if arguments are renderbuffers, false if arguments are buffers bool arguments_are_renderbuffers_; std::unique_ptr argument_identifier_1_; std::unique_ptr argument_identifier_2_; + std::vector format_entries_; }; } // namespace shadertrap diff --git a/src/libshadertrap/include/libshadertrap/parser.h b/src/libshadertrap/include/libshadertrap/parser.h index aeafa7d..723e1b7 100644 --- a/src/libshadertrap/include/libshadertrap/parser.h +++ b/src/libshadertrap/include/libshadertrap/parser.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -62,7 +63,8 @@ class Parser { bool ParseParameters( const std::map>& parameter_parsers, - const std::map& mutually_exclusive); + const std::map& mutually_exclusive, + const std::set& optional_params); bool ParseParameters( const std::map>& parameter_parsers); diff --git a/src/libshadertrap/src/checker.cc b/src/libshadertrap/src/checker.cc index 97e916b..e3cb29f 100644 --- a/src/libshadertrap/src/checker.cc +++ b/src/libshadertrap/src/checker.cc @@ -187,10 +187,66 @@ bool Checker::VisitAssertEqual(CommandAssertEqual* command_assert_equal) { std::to_string(buffer1->GetSizeBytes()) + " of '" + operand1_token.GetText() + "' at " + operand1_token.GetLocationString()); - return false; + found_errors = true; } } - return true; + auto& format_entries = command_assert_equal->GetFormatEntries(); + if (!format_entries.empty()) { + size_t total_count_bytes = 0; + for (const auto& format_entry : format_entries) { + if (format_entry.count == 0) { + message_consumer_->Message( + MessageConsumer::Severity::kError, format_entry.token.get(), + "The count for a formatting entry must be positive"); + found_errors = true; + } + switch (format_entry.kind) { + case CommandAssertEqual::FormatEntry::Kind::kByte: + case CommandAssertEqual::FormatEntry::Kind::kSkip: + if (format_entry.count % 4 != 0) { + message_consumer_->Message( + MessageConsumer::Severity::kError, format_entry.token.get(), + "The count for a '" + + Tokenizer::KeywordToString( + format_entry.kind == + CommandAssertEqual::FormatEntry::Kind::kByte + ? Token::Type::kKeywordTypeByte + : Token::Type::kKeywordSkipBytes) + + "' formatting entry must be a multiple of 4; found " + + std::to_string(format_entry.count)); + found_errors = true; + } + total_count_bytes += format_entry.count; + break; + case CommandAssertEqual::FormatEntry::Kind::kFloat: + case CommandAssertEqual::FormatEntry::Kind::kInt: + case CommandAssertEqual::FormatEntry::Kind::kUint: + total_count_bytes += format_entry.count * 4; + break; + } + } + + auto* buffer1 = created_buffers_.at(operand1_token.GetText()); + auto* buffer2 = created_buffers_.at(operand2_token.GetText()); + const size_t expected_bytes = buffer1->GetSizeBytes(); + + if (total_count_bytes != expected_bytes) { + message_consumer_->Message( + MessageConsumer::Severity::kError, + command_assert_equal->GetFormatEntries()[0].token.get(), + "The number of bytes specified in the formatting of '" + + buffer1->GetResultIdentifier() + "(" + + buffer2->GetResultIdentifier() + ")" + "' is " + + std::to_string(total_count_bytes) + ", but '" + + buffer1->GetResultIdentifier() + "(" + + buffer2->GetResultIdentifier() + ")" + + "' was declared with size " + std::to_string(expected_bytes) + + " byte" + (expected_bytes > 1 ? "s" : "") + " at " + + buffer1->GetStartToken().GetLocationString()); + found_errors = true; + } + } + return !found_errors; } bool Checker::VisitAssertPixels(CommandAssertPixels* command_assert_pixels) { diff --git a/src/libshadertrap/src/command_assert_equal.cc b/src/libshadertrap/src/command_assert_equal.cc index 6634eb4..d48b025 100644 --- a/src/libshadertrap/src/command_assert_equal.cc +++ b/src/libshadertrap/src/command_assert_equal.cc @@ -21,11 +21,22 @@ namespace shadertrap { CommandAssertEqual::CommandAssertEqual( - std::unique_ptr start_token, bool arguments_are_renderbuffers, + std::unique_ptr start_token, + std::unique_ptr argument_identifier_1, + std::unique_ptr argument_identifier_2, + std::vector format_entries) + : Command(std::move(start_token)), + arguments_are_renderbuffers_(false), + argument_identifier_1_(std::move(argument_identifier_1)), + argument_identifier_2_(std::move(argument_identifier_2)), + format_entries_(std::move(format_entries)) {} + +CommandAssertEqual::CommandAssertEqual( + std::unique_ptr start_token, std::unique_ptr argument_identifier_1, std::unique_ptr argument_identifier_2) : Command(std::move(start_token)), - arguments_are_renderbuffers_(arguments_are_renderbuffers), + arguments_are_renderbuffers_(true), argument_identifier_1_(std::move(argument_identifier_1)), argument_identifier_2_(std::move(argument_identifier_2)) {} diff --git a/src/libshadertrap/src/executor.cc b/src/libshadertrap/src/executor.cc index 90eb126..dbf6405 100644 --- a/src/libshadertrap/src/executor.cc +++ b/src/libshadertrap/src/executor.cc @@ -29,6 +29,7 @@ #include #include +#include "libshadertrap/make_unique.h" #include "libshadertrap/texture_parameter.h" #include "libshadertrap/token.h" #include "libshadertrap/uniform_value.h" @@ -1210,32 +1211,157 @@ bool Executor::CheckEqualBuffers(CommandAssertEqual* assert_equal) { GL_ARRAY_BUFFER, 0, static_cast(buffer_size[index]), GL_MAP_READ_BIT)); if (mapped_buffer[index] == nullptr) { - // TODO(afd): If index == 1 should we unmap buffers[0] before - // returning? Or are we OK so long as we eventually destroy that buffer? GL_CHECKERR(&assert_equal->GetStartToken(), "glMapBufferRange"); return false; } } + std::vector& format_entries = + assert_equal->GetFormatEntries(); + + if (format_entries.empty()) { + // No format entries were specified, so a default byte-based format entry, + // based on the size of the buffers, is used. + const Token& start_token = assert_equal->GetStartToken(); + format_entries.push_back( + {MakeUnique(start_token.GetType(), start_token.GetLine(), 0U), + CommandAssertEqual::FormatEntry::Kind::kByte, + static_cast(buffer_size[0])}); + } + bool result = true; - for (size_t index = 0; index < static_cast(buffer_size[0]); index++) { - // We only get here if the calls to glMapBufferRange succeeded, in which - // case the contents of |mapped_buffer| cannot be null. - uint8_t value_1 = - mapped_buffer[0][index]; // NOLINT(clang-analyzer-core.NullDereference) - uint8_t value_2 = - mapped_buffer[1][index]; // NOLINT(clang-analyzer-core.NullDereference) - if (value_1 != value_2) { - std::stringstream stringstream; - stringstream << "Byte mismatch at index " << index << ": " - << assert_equal->GetArgumentIdentifier1() << "[" << index - << "] == " << static_cast(value_1) << ", " - << assert_equal->GetArgumentIdentifier2() << "[" << index - << "] == " << static_cast(value_2); - message_consumer_->Message(MessageConsumer::Severity::kError, - &assert_equal->GetStartToken(), - stringstream.str()); - result = false; + size_t offset = 0; + + for (auto& format_entry : format_entries) { + switch (format_entry.kind) { + case CommandAssertEqual::FormatEntry::Kind::kSkip: + offset += format_entry.count; + break; + case CommandAssertEqual::FormatEntry::Kind::kByte: { + for (size_t index = offset; index < format_entry.count; index++) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + uint8_t value_1 = mapped_buffer[0][index]; + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + uint8_t value_2 = mapped_buffer[1][index]; + if (value_1 != value_2) { + std::stringstream stringstream; + stringstream << "Byte mismatch at index " << index << ": " + << assert_equal->GetArgumentIdentifier1() << "[" + << index << "] == " << static_cast(value_1) + << ", " << assert_equal->GetArgumentIdentifier2() + << "[" << index + << "] == " << static_cast(value_2); + message_consumer_->Message(MessageConsumer::Severity::kError, + &assert_equal->GetStartToken(), + stringstream.str()); + result = false; + } + } + offset += format_entry.count; + break; + } + case CommandAssertEqual::FormatEntry::Kind::kFloat: { + float* float_region[2]{nullptr, nullptr}; + for (auto index : {0, 1}) { + float_region[index] = + // cppcheck-suppress invalidPointerCast + reinterpret_cast(mapped_buffer[index] + offset); + } + + for (size_t index = 0; index < format_entry.count; index++) { + if (float_region[0] != nullptr && float_region[1] != nullptr) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + float value_1 = float_region[0][index]; + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + float value_2 = float_region[1][index]; + // We compare data using memcmp to look for byte-level mismatches, + // and then report any mismatches at the floating-point level. This + // avoids performing floating-point comparisons, and associated + // issues related to special values. + if (std::memcmp(&float_region[0][index], &float_region[1][index], + sizeof(float)) != 0) { + std::stringstream stringstream; + size_t float_index = sizeof(float) * index + offset; + stringstream << "Float mismatch at byte index " << float_index + << ": " << assert_equal->GetArgumentIdentifier1() + << "[" << float_index + << "] == " << static_cast(value_1) << ", " + << assert_equal->GetArgumentIdentifier2() << "[" + << float_index + << "] == " << static_cast(value_2); + message_consumer_->Message(MessageConsumer::Severity::kError, + &assert_equal->GetStartToken(), + stringstream.str()); + result = false; + } + } + } + offset += format_entry.count * sizeof(float); + break; + } + case CommandAssertEqual::FormatEntry::Kind::kInt: { + int32_t* int_region[2]{nullptr, nullptr}; + for (auto index : {0, 1}) { + int_region[index] = + reinterpret_cast(mapped_buffer[index] + offset); + } + + for (size_t index = 0; index < format_entry.count; index++) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + int32_t value_1 = int_region[0][index]; + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + int32_t value_2 = int_region[1][index]; + if (value_1 != value_2) { + size_t int_index = index * sizeof(int32_t) + offset; + std::stringstream stringstream; + stringstream << "Integer mismatch at byte_index " << int_index + << ": " << assert_equal->GetArgumentIdentifier1() + << "[" << int_index + << "] == " << static_cast(value_1) << ", " + << assert_equal->GetArgumentIdentifier2() << "[" + << int_index + << "] == " << static_cast(value_2); + message_consumer_->Message(MessageConsumer::Severity::kError, + &assert_equal->GetStartToken(), + stringstream.str()); + result = false; + } + } + offset += format_entry.count * sizeof(int32_t); + break; + } + case CommandAssertEqual::FormatEntry::Kind::kUint: { + uint32_t* uint_region[2]{nullptr, nullptr}; + for (auto index : {0, 1}) { + uint_region[index] = + reinterpret_cast(mapped_buffer[index] + offset); + } + + for (size_t index = 0; index < format_entry.count; index++) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + uint32_t value_1 = uint_region[0][index]; + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + uint32_t value_2 = uint_region[1][index]; + if (value_1 != value_2) { + size_t uint_index = index * sizeof(uint32_t) + offset; + std::stringstream stringstream; + stringstream << "Unsigned integer mismatch at byte index " + << uint_index << ": " + << assert_equal->GetArgumentIdentifier1() << "[" + << uint_index + << "] == " << static_cast(value_1) << ", " + << assert_equal->GetArgumentIdentifier2() << "[" + << uint_index + << "] == " << static_cast(value_2); + message_consumer_->Message(MessageConsumer::Severity::kError, + &assert_equal->GetStartToken(), + stringstream.str()); + result = false; + } + } + offset += format_entry.count * sizeof(uint32_t); + break; + } } } diff --git a/src/libshadertrap/src/parser.cc b/src/libshadertrap/src/parser.cc index b4356cd..d0fc4e1 100644 --- a/src/libshadertrap/src/parser.cc +++ b/src/libshadertrap/src/parser.cc @@ -210,6 +210,7 @@ bool Parser::ParseCommandAssertEqual() { std::unique_ptr argument_identifier_1; std::unique_ptr argument_identifier_2; bool arguments_are_renderbuffers = false; + std::vector format_entries; if (!ParseParameters( {{Token::Type::kKeywordBuffers, [this, &arguments_are_renderbuffers, &argument_identifier_1, @@ -254,15 +255,83 @@ bool Parser::ParseCommandAssertEqual() { } argument_identifier_2 = std::move(token); return true; + }}, + + {Token::Type::kKeywordFormat, + [this, &format_entries, &start_token]() -> bool { + bool seen_at_least_one_format_entry = false; + while (true) { + CommandAssertEqual::FormatEntry::Kind kind; + switch (tokenizer_->PeekNextToken()->GetType()) { + case Token::Type::kKeywordSkipBytes: + seen_at_least_one_format_entry = true; + kind = CommandAssertEqual::FormatEntry::Kind::kSkip; + break; + case Token::Type::kKeywordTypeByte: + seen_at_least_one_format_entry = true; + kind = CommandAssertEqual::FormatEntry::Kind::kByte; + break; + case Token::Type::kKeywordTypeFloat: + seen_at_least_one_format_entry = true; + kind = CommandAssertEqual::FormatEntry::Kind::kFloat; + break; + case Token::Type::kKeywordTypeInt: + seen_at_least_one_format_entry = true; + kind = CommandAssertEqual::FormatEntry::Kind::kInt; + break; + case Token::Type::kKeywordTypeUint: + seen_at_least_one_format_entry = true; + kind = CommandAssertEqual::FormatEntry::Kind::kUint; + break; + default: + // Handles the case when no identifier is specified after + // FORMAT. + if (!seen_at_least_one_format_entry) { + message_consumer_->Message( + MessageConsumer::Severity::kError, start_token.get(), + "Missing identifier after FORMAT"); + } + return seen_at_least_one_format_entry; + } + auto format_start_token = tokenizer_->NextToken(); + size_t count; + auto maybe_count = ParseUint32("count"); + + // Handles the case when the count after the FORMAT option is 0 + // or is not specified + if (!maybe_count.first) { + return false; + } + count = maybe_count.second; + + format_entries.push_back( + {std::move(format_start_token), kind, count}); + } }}}, // BUFFERS and RENDERBUFFERS are mutually exclusive parameters - {{Token::Type::kKeywordBuffers, - Token::Type::kKeywordRenderbuffers}})) { + {{Token::Type::kKeywordBuffers, Token::Type::kKeywordRenderbuffers}}, + + {Token::Type::kKeywordFormat})) { return false; } + if (arguments_are_renderbuffers) { + if (!format_entries.empty()) { + message_consumer_->Message(MessageConsumer::Severity::kError, + start_token.get(), + "FORMAT specifier cannot be set" + "for renderbuffers arguments"); + return false; + } + parsed_commands_.push_back(MakeUnique( + std::move(start_token), std::move(argument_identifier_1), + std::move(argument_identifier_2))); + return true; + } + parsed_commands_.push_back(MakeUnique( - std::move(start_token), arguments_are_renderbuffers, - std::move(argument_identifier_1), std::move(argument_identifier_2))); + std::move(start_token), std::move(argument_identifier_1), + std::move(argument_identifier_2), std::move(format_entries))); + return true; } @@ -1483,7 +1552,7 @@ bool Parser::ParseCommandSetUniform() { return true; }}}, // LOCATION and NAME are mutually exclusive - {{Token::Type::kKeywordLocation, Token::Type::kKeywordName}})) { + {{Token::Type::kKeywordLocation, Token::Type::kKeywordName}}, {})) { return false; } auto maybe_uniform_value = @@ -1582,7 +1651,8 @@ std::pair Parser::ProcessUniformValue( bool Parser::ParseParameters( const std::map>& parameter_parsers, - const std::map& mutually_exclusive) { + const std::map& mutually_exclusive, + const std::set& optional_params) { // Check that any token types that are regarded as mutually exclusive do have // associated parser entries. for (const auto& entry : mutually_exclusive) { @@ -1645,6 +1715,7 @@ bool Parser::ParseParameters( for (const auto& entry : parameter_parsers) { if (already_handled.count(entry.first) == 0 && + optional_params.count(entry.first) == 0 && observed.count(entry.first) == 0) { message_consumer_->Message( MessageConsumer::Severity::kError, tokenizer_->PeekNextToken().get(), @@ -1658,7 +1729,7 @@ bool Parser::ParseParameters( bool Parser::ParseParameters( const std::map>& parameter_parsers) { - return ParseParameters(parameter_parsers, {}); + return ParseParameters(parameter_parsers, {}, {}); } std::pair Parser::ParseVertexAttributeInfo() { diff --git a/src/libshadertraptest/src/checker_test.cc b/src/libshadertraptest/src/checker_test.cc index fac6334..64932f0 100644 --- a/src/libshadertraptest/src/checker_test.cc +++ b/src/libshadertraptest/src/checker_test.cc @@ -1571,5 +1571,82 @@ DUMP_BUFFER_TEXT BUFFER buf FILE "temp.txt" FORMAT float 0 float 12 message_consumer.GetMessageString(0)); } +TEST_F(CheckerTestFixture, AssertEqualFailCheckerTest1) { + std::string program = + R"(GL 4.5 +CREATE_BUFFER buf1 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +CREATE_BUFFER buf2 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +ASSERT_EQUAL BUFFERS buf1 buf2 FORMAT float 3 +)"; + CollectingMessageConsumer message_consumer; + Parser parser(program, &message_consumer); + ASSERT_TRUE(parser.Parse()); + auto parsed_program = parser.GetParsedProgram(); + Checker checker(&message_consumer, parsed_program->GetApiVersion()); + ASSERT_FALSE(checker.VisitCommands(parsed_program.get())); + ASSERT_EQ(1, message_consumer.GetNumMessages()); + ASSERT_EQ( + "ERROR: 4:39: The number of bytes specified in the formatting of " + "'buf1(buf2)' is 12, but 'buf1(buf2)' was declared with size 16 bytes at " + "2:1", + message_consumer.GetMessageString(0)); +} + +TEST_F(CheckerTestFixture, AssertEqualFailCheckerTest2) { + std::string program = + R"(GL 4.5 +CREATE_BUFFER buf1 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +CREATE_BUFFER buf2 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +ASSERT_EQUAL BUFFERS buf1 buf2 FORMAT float 0 float 4 +)"; + CollectingMessageConsumer message_consumer; + Parser parser(program, &message_consumer); + ASSERT_TRUE(parser.Parse()); + auto parsed_program = parser.GetParsedProgram(); + Checker checker(&message_consumer, parsed_program->GetApiVersion()); + ASSERT_FALSE(checker.VisitCommands(parsed_program.get())); + ASSERT_EQ(1, message_consumer.GetNumMessages()); + ASSERT_EQ("ERROR: 4:39: The count for a formatting entry must be positive", + message_consumer.GetMessageString(0)); +} + +TEST_F(CheckerTestFixture, AssertEqualFailCheckerTest3) { + std::string program = + R"(GL 4.5 +CREATE_BUFFER buf1 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +CREATE_BUFFER buf2 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +ASSERT_EQUAL BUFFERS buf1 buf2 FORMAT SKIP_BYTES 0 float 4 +)"; + CollectingMessageConsumer message_consumer; + Parser parser(program, &message_consumer); + ASSERT_TRUE(parser.Parse()); + auto parsed_program = parser.GetParsedProgram(); + Checker checker(&message_consumer, parsed_program->GetApiVersion()); + ASSERT_FALSE(checker.VisitCommands(parsed_program.get())); + ASSERT_EQ(1, message_consumer.GetNumMessages()); + ASSERT_EQ("ERROR: 4:39: The count for a formatting entry must be positive", + message_consumer.GetMessageString(0)); +} + +TEST_F(CheckerTestFixture, AssertEqualFailCheckerTest4) { + std::string program = + R"(GL 4.5 +CREATE_BUFFER buf1 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +CREATE_BUFFER buf2 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +ASSERT_EQUAL BUFFERS buf1 buf2 FORMAT SKIP_BYTES 3 float 4 +)"; + CollectingMessageConsumer message_consumer; + Parser parser(program, &message_consumer); + ASSERT_TRUE(parser.Parse()); + auto parsed_program = parser.GetParsedProgram(); + Checker checker(&message_consumer, parsed_program->GetApiVersion()); + ASSERT_FALSE(checker.VisitCommands(parsed_program.get())); + ASSERT_EQ(2, message_consumer.GetNumMessages()); + ASSERT_EQ( + "ERROR: 4:39: The count for a 'SKIP_BYTES' formatting entry must be a " + "multiple of 4; found 3", + message_consumer.GetMessageString(0)); +} + } // namespace } // namespace shadertrap diff --git a/src/libshadertraptest/src/parser_test.cc b/src/libshadertraptest/src/parser_test.cc index 28d1fa4..aba566e 100644 --- a/src/libshadertraptest/src/parser_test.cc +++ b/src/libshadertraptest/src/parser_test.cc @@ -388,5 +388,50 @@ DUMP_BUFFER_TEXT BUFFER buf FILE "temp.txt" FORMAT float 4 4 message_consumer.GetMessageString(0)); } +TEST(ParserTest, AssertEqualMissingIdentifier) { + std::string program = + R"(GL 4.5 +CREATE_BUFFER buf1 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +CREATE_BUFFER buf2 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +ASSERT_EQUAL BUFFERS buf1 buf2 FORMAT +)"; + CollectingMessageConsumer message_consumer; + Parser parser(program, &message_consumer); + ASSERT_FALSE(parser.Parse()); + ASSERT_EQ(1, message_consumer.GetNumMessages()); + ASSERT_EQ("ERROR: 4:1: Missing identifier after FORMAT", + message_consumer.GetMessageString(0)); +} + +TEST(ParserTest, AssertEqualMissingCount) { + std::string program = + R"(GL 4.5 +CREATE_BUFFER buf1 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +CREATE_BUFFER buf2 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +ASSERT_EQUAL BUFFERS buf1 buf2 FORMAT float +)"; + CollectingMessageConsumer message_consumer; + Parser parser(program, &message_consumer); + ASSERT_FALSE(parser.Parse()); + ASSERT_EQ(1, message_consumer.GetNumMessages()); + ASSERT_EQ("ERROR: 5:1: Expected integer count, got ''", + message_consumer.GetMessageString(0)); +} + +TEST(ParserTest, AssertEqualNegativeCount) { + std::string program = + R"(GL 4.5 +CREATE_BUFFER buf1 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +CREATE_BUFFER buf2 SIZE_BYTES 16 INIT_VALUES float 1.0 2.0 3.0 4.0 +ASSERT_EQUAL BUFFERS buf1 buf2 FORMAT float -1 +)"; + CollectingMessageConsumer message_consumer; + Parser parser(program, &message_consumer); + ASSERT_FALSE(parser.Parse()); + ASSERT_EQ(1, message_consumer.GetNumMessages()); + ASSERT_EQ("ERROR: 4:45: Expected non-negative integer count, got '-1'", + message_consumer.GetMessageString(0)); +} + } // namespace } // namespace shadertrap