CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
958 lines
29 KiB
C++
958 lines
29 KiB
C++
// MIT License
|
|
//
|
|
// Copyright (c) 2024-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <string>
|
|
#include "gfx9/gfx9token.h"
|
|
#include "gfx9/gfx9wave.h"
|
|
|
|
namespace gfx9
|
|
{
|
|
extern std::string print_token(Token& token);
|
|
}
|
|
namespace gfx9
|
|
{
|
|
extern void convertPerfEventsTs(std::vector<att_perfevent_t>& vec, int64_t interval);
|
|
}
|
|
|
|
using namespace gfx9;
|
|
|
|
// Tests for Token field parsing (using get_bits macro behavior)
|
|
|
|
TEST(Gfx9GroupIdTest, ParsesFieldsCorrectly)
|
|
{
|
|
// cu is bits 6-9, sh is bit 5, wave is bits 10-13, simd is bits 14-15
|
|
// cu=5 (0101), sh=1, wave=3, simd=2
|
|
// bits: simd(14-15)=10, wave(10-13)=0011, cu(6-9)=0101, sh(5)=1
|
|
uint64_t val = (2ull << 14) | (3ull << 10) | (5ull << 6) | (1ull << 5);
|
|
|
|
group_id gid(val);
|
|
|
|
EXPECT_EQ(gid.cu, 5);
|
|
EXPECT_EQ(gid.sh, 1);
|
|
EXPECT_EQ(gid.wave, 3);
|
|
EXPECT_EQ(gid.simd, 2);
|
|
}
|
|
|
|
TEST(Gfx9WaveTest, ParsesFieldsCorrectly)
|
|
{
|
|
// Additional fields: pipe(16-17), me(18-19), count(22-28)
|
|
// pipe=2, me=1, count=50
|
|
uint64_t val = (2ull << 14) | (3ull << 10) | (5ull << 6) | (1ull << 5); // base group_id
|
|
val |= (2ull << 16); // pipe
|
|
val |= (1ull << 18); // me
|
|
val |= (50ull << 22); // count
|
|
|
|
Wave wave(val);
|
|
|
|
EXPECT_EQ(wave.cu, 5);
|
|
EXPECT_EQ(wave.pipe, 2);
|
|
EXPECT_EQ(wave.me, 1);
|
|
EXPECT_EQ(wave.count, 50);
|
|
}
|
|
|
|
TEST(Gfx9RegTest, ParsesFieldsCorrectly)
|
|
{
|
|
// pipe(5-6), me(7-8), regaddr(16-31), regdata(32-63)
|
|
uint64_t val = (3ull << 5); // pipe=3
|
|
val |= (2ull << 7); // me=2 -> (2+1)&1 = 1
|
|
val |= (0x1234ull << 16); // regaddr
|
|
val |= (0xDEADBEEFull << 32); // regdata
|
|
|
|
Reg reg(val);
|
|
|
|
EXPECT_EQ(reg.pipe, 3);
|
|
EXPECT_EQ(reg.me, 1); // (2+1)&1
|
|
EXPECT_EQ(reg.regaddr, 0x1234);
|
|
EXPECT_EQ(reg.regdata, 0xDEADBEEF);
|
|
}
|
|
|
|
TEST(Gfx9RegCsTest, ParsesFieldsCorrectly)
|
|
{
|
|
// pipe(5-6), me(7-8), regaddr(9-15), regdata(16-47)
|
|
uint64_t val = (1ull << 5); // pipe=1
|
|
val |= (0ull << 7); // me=0 -> (0+1)&1 = 1
|
|
val |= (0x42ull << 9); // regaddr (7 bits)
|
|
val |= (0xCAFEBABEull << 16); // regdata (32 bits)
|
|
|
|
RegCs regcs(val);
|
|
|
|
EXPECT_EQ(regcs.pipe, 1);
|
|
EXPECT_EQ(regcs.me, 1); // (0+1)&1
|
|
EXPECT_EQ(regcs.regaddr, 0x42);
|
|
EXPECT_EQ(regcs.regdata, 0xCAFEBABE);
|
|
}
|
|
|
|
TEST(Gfx9MiscTest, ParsesFieldsCorrectly)
|
|
{
|
|
// sh(12), misc_type(13-15)
|
|
uint64_t val = (1ull << 12); // sh=1
|
|
val |= (5ull << 13); // misc_type=5
|
|
|
|
Misc misc(val);
|
|
|
|
EXPECT_EQ(misc.sh, 1);
|
|
EXPECT_EQ(misc.misc_type, 5);
|
|
}
|
|
|
|
TEST(Gfx9MsgInstTest, ParsesFieldsCorrectly)
|
|
{
|
|
// wave(5-8), simd(9-10), inst_type(11-15)
|
|
uint64_t val = (7ull << 5); // wave=7
|
|
val |= (3ull << 9); // simd=3
|
|
val |= (12ull << 11); // inst_type=12
|
|
|
|
MsgInst inst(val);
|
|
|
|
EXPECT_EQ(inst.wave, 7);
|
|
EXPECT_EQ(inst.simd, 3);
|
|
EXPECT_EQ(inst.inst_type, 12);
|
|
}
|
|
|
|
TEST(Gfx9MsgInstPcTest, ParsesFieldsCorrectly)
|
|
{
|
|
// wave(5-8), simd(9-10), err(15), pc(16-63)
|
|
uint64_t val = (5ull << 5); // wave=5
|
|
val |= (2ull << 9); // simd=2
|
|
val |= (1ull << 15); // err=1
|
|
val |= (0x123456789ABCull << 16); // pc
|
|
|
|
MsgInstPc instpc(val);
|
|
|
|
EXPECT_EQ(instpc.wave, 5);
|
|
EXPECT_EQ(instpc.simd, 2);
|
|
EXPECT_EQ(instpc.err, 1);
|
|
EXPECT_EQ(instpc.pc, 0x123456789ABCull);
|
|
}
|
|
|
|
TEST(Gfx9IssueTest, ParsesFieldsCorrectly)
|
|
{
|
|
// simd(5-6), inst[0-9] each 2 bits starting at bit 8
|
|
uint64_t val = (2ull << 5); // simd=2
|
|
|
|
// Set inst[0]=1, inst[1]=2, inst[2]=3, rest=0
|
|
val |= (1ull << 8); // inst[0]
|
|
val |= (2ull << 10); // inst[1]
|
|
val |= (3ull << 12); // inst[2]
|
|
|
|
Issue issue(val);
|
|
|
|
EXPECT_EQ(issue.simd, 2);
|
|
EXPECT_EQ(issue.inst[0], 1);
|
|
EXPECT_EQ(issue.inst[1], 2);
|
|
EXPECT_EQ(issue.inst[2], 3);
|
|
EXPECT_EQ(issue.inst[3], 0);
|
|
}
|
|
|
|
TEST(Gfx9MsgPerfTest, ParsesFieldsCorrectly)
|
|
{
|
|
// sh(5), cu(6-9), cntr_bank(10-11), cntr[0-3] each 13 bits
|
|
uint64_t val = (1ull << 5); // sh=1
|
|
val |= (10ull << 6); // cu=10
|
|
val |= (2ull << 10); // cntr_bank=2
|
|
val |= (100ull << 12); // cntr[0]
|
|
val |= (200ull << 25); // cntr[1]
|
|
val |= (300ull << 38); // cntr[2]
|
|
val |= (400ull << 51); // cntr[3]
|
|
|
|
MsgPerf perf(val);
|
|
|
|
EXPECT_EQ(perf.sh, 1);
|
|
EXPECT_EQ(perf.cu, 10);
|
|
EXPECT_EQ(perf.cntr_bank, 2);
|
|
EXPECT_EQ(perf.cntr[0], 100);
|
|
EXPECT_EQ(perf.cntr[1], 200);
|
|
EXPECT_EQ(perf.cntr[2], 300);
|
|
EXPECT_EQ(perf.cntr[3], 400);
|
|
}
|
|
|
|
TEST(Gfx9UserDataTest, ParsesFieldsCorrectly)
|
|
{
|
|
// Inherits from group_id, adds data(16-47)
|
|
uint64_t val = (2ull << 14) | (3ull << 10) | (5ull << 6) | (1ull << 5); // group_id
|
|
val |= (0xABCD1234ull << 16); // data
|
|
|
|
UserData ud(val);
|
|
|
|
EXPECT_EQ(ud.cu, 5);
|
|
EXPECT_EQ(ud.simd, 2);
|
|
EXPECT_EQ(ud.data, 0xABCD1234);
|
|
}
|
|
|
|
// Tests for Token class
|
|
TEST(Gfx9TokenTest, ParsesTimeToken)
|
|
{
|
|
// type=TOKEN_TIME (1), time in bits 16-63
|
|
uint64_t val = TOKEN_TIME; // type in bits 0-3
|
|
val |= (0x123456789ABCull << 16); // time
|
|
|
|
Token token(TOKEN_TIME, val);
|
|
|
|
EXPECT_EQ(token.type, TOKEN_TIME);
|
|
EXPECT_EQ(token.time, 0x123456789ABCull);
|
|
}
|
|
|
|
TEST(Gfx9TokenTest, ParsesMiscToken)
|
|
{
|
|
uint64_t val = TOKEN_MISC;
|
|
val |= (1ull << 12); // sh
|
|
val |= (3ull << 13); // misc_type
|
|
|
|
Token token(TOKEN_MISC, val);
|
|
|
|
EXPECT_EQ(token.type, TOKEN_MISC);
|
|
EXPECT_EQ(token.fields.misc.sh, 1);
|
|
EXPECT_EQ(token.fields.misc.misc_type, 3);
|
|
}
|
|
|
|
TEST(Gfx9TokenTest, DeltaForMiscToken)
|
|
{
|
|
// For type==0 (MISC), delta is bits 4-11
|
|
uint64_t val = TOKEN_MISC;
|
|
val |= (0xABull << 4); // delta
|
|
|
|
Token token(TOKEN_MISC, val);
|
|
|
|
EXPECT_EQ(token.delta, 0xAB);
|
|
}
|
|
|
|
TEST(Gfx9TokenTest, DeltaForOtherTokens)
|
|
{
|
|
// For type!=0, delta is just bit 4
|
|
uint64_t val = TOKEN_TIME;
|
|
val |= (1ull << 4); // delta
|
|
|
|
Token token(TOKEN_TIME, val);
|
|
|
|
EXPECT_EQ(token.delta, 1);
|
|
}
|
|
|
|
// Tests for CSRegisterHandlerGFX9
|
|
TEST(CSRegisterHandlerGFX9Test, IsRegCS)
|
|
{
|
|
CSRegisterHandlerGFX9 handler;
|
|
|
|
EXPECT_TRUE(handler.IsRegCS(5)); // SQTT_TOKEN_REG_CS
|
|
EXPECT_TRUE(handler.IsRegCS(15)); // SQTT_TOKEN_REG_CS_PRIV
|
|
EXPECT_FALSE(handler.IsRegCS(2)); // SQTT_TOKEN_REG (not CS)
|
|
EXPECT_FALSE(handler.IsRegCS(0));
|
|
}
|
|
|
|
TEST(CSRegisterHandlerGFX9Test, IsRegNoCS)
|
|
{
|
|
CSRegisterHandlerGFX9 handler;
|
|
|
|
EXPECT_TRUE(handler.IsRegNoCS(2)); // SQTT_TOKEN_REG
|
|
EXPECT_FALSE(handler.IsRegNoCS(5)); // SQTT_TOKEN_REG_CS
|
|
EXPECT_FALSE(handler.IsRegNoCS(15)); // SQTT_TOKEN_REG_CS_PRIV
|
|
}
|
|
|
|
TEST(CSRegisterHandlerGFX9Test, IsUserdata)
|
|
{
|
|
CSRegisterHandlerGFX9 handler;
|
|
|
|
EXPECT_TRUE(handler.IsUserdata(0xC340));
|
|
EXPECT_TRUE(handler.IsUserdata(0xC341));
|
|
EXPECT_TRUE(handler.IsUserdata(0xC342));
|
|
EXPECT_TRUE(handler.IsUserdata(0xC343));
|
|
EXPECT_FALSE(handler.IsUserdata(0xC33F));
|
|
EXPECT_FALSE(handler.IsUserdata(0xC344));
|
|
}
|
|
|
|
TEST(CSRegisterHandlerGFX9Test, IsUserdataSpecific)
|
|
{
|
|
CSRegisterHandlerGFX9 handler;
|
|
|
|
EXPECT_TRUE(handler.IsUserdata0(0xC340));
|
|
EXPECT_FALSE(handler.IsUserdata0(0xC341));
|
|
|
|
EXPECT_TRUE(handler.IsUserdata1(0xC341));
|
|
EXPECT_FALSE(handler.IsUserdata1(0xC340));
|
|
|
|
EXPECT_TRUE(handler.IsUserdata2(0xC342));
|
|
EXPECT_FALSE(handler.IsUserdata2(0xC343));
|
|
|
|
EXPECT_TRUE(handler.IsUserdata3(0xC343));
|
|
EXPECT_FALSE(handler.IsUserdata3(0xC342));
|
|
}
|
|
|
|
// Tests for MITokenGenerator - OOB safety and basic functionality
|
|
TEST(MITokenGeneratorTest, ConstructorThrowsOnNullBuffer)
|
|
{
|
|
EXPECT_THROW(MITokenGenerator(nullptr, 100, 0, 0), std::exception);
|
|
}
|
|
|
|
TEST(MITokenGeneratorTest, ConstructorThrowsOnZeroSize)
|
|
{
|
|
uint8_t buffer[16] = {0};
|
|
EXPECT_THROW(MITokenGenerator(buffer, 0, 0, 0), std::exception);
|
|
}
|
|
|
|
TEST(MITokenGeneratorTest, ValidBufferCreatesGenerator)
|
|
{
|
|
std::vector<uint8_t> buffer(32, 0);
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
EXPECT_TRUE(gen.valid());
|
|
}
|
|
|
|
TEST(MITokenGeneratorTest, SmallBufferProcessesWithoutCrash)
|
|
{
|
|
// Minimum buffer size for one 16-bit token
|
|
std::vector<uint8_t> buffer(2, 0);
|
|
// Type 0 (MISC) = 16 bits
|
|
buffer[0] = TOKEN_MISC; // type = 0
|
|
buffer[1] = 0;
|
|
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
// Should process without crashing
|
|
auto token = gen.next();
|
|
EXPECT_EQ(token.type, TOKEN_MISC);
|
|
}
|
|
|
|
TEST(MITokenGeneratorTest, ParsesTimeToken)
|
|
{
|
|
// TIME token is 64 bits (8 bytes)
|
|
// Type 1 = TOKEN_TIME, length = 64 bits
|
|
// TIME tokens are consumed internally by MITokenGenerator
|
|
// So we need to follow with another token to verify TIME was processed
|
|
std::vector<uint8_t> buffer(10, 0);
|
|
|
|
// TIME token (8 bytes)
|
|
buffer[0] = TOKEN_TIME; // type = 1
|
|
buffer[1] = 0;
|
|
buffer[2] = 0x12;
|
|
buffer[3] = 0x34;
|
|
buffer[4] = 0x56;
|
|
buffer[5] = 0x78;
|
|
buffer[6] = 0x9A;
|
|
buffer[7] = 0xBC;
|
|
|
|
// Follow with MISC token (2 bytes) to have something returned
|
|
buffer[8] = TOKEN_MISC;
|
|
buffer[9] = 0;
|
|
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
// TIME token is consumed internally, so next() returns MISC
|
|
auto token = gen.next();
|
|
EXPECT_EQ(token.type, TOKEN_MISC);
|
|
}
|
|
|
|
TEST(MITokenGeneratorTest, ParsesWaveStartToken)
|
|
{
|
|
// WAVE_START token is 32 bits (4 bytes) per token_len_dict[3]
|
|
// Type 3 = TOKEN_WAVE_START
|
|
std::vector<uint8_t> buffer(4, 0);
|
|
|
|
// Build a valid WAVE_START token
|
|
// type(0-3)=3, delta(4)=0, sh(5)=0, cu(6-9)=5, wave(10-13)=3, simd(14-15)=2
|
|
// pipe(16-17)=0, me(18-19)=0, count(22-28)=0
|
|
uint32_t token_val = TOKEN_WAVE_START; // type = 3
|
|
token_val |= (0 << 4); // delta
|
|
token_val |= (0 << 5); // sh
|
|
token_val |= (5 << 6); // cu = 5
|
|
token_val |= (3 << 10); // wave = 3
|
|
token_val |= (2 << 14); // simd = 2
|
|
|
|
buffer[0] = token_val & 0xFF;
|
|
buffer[1] = (token_val >> 8) & 0xFF;
|
|
buffer[2] = (token_val >> 16) & 0xFF;
|
|
buffer[3] = (token_val >> 24) & 0xFF;
|
|
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
auto token = gen.next();
|
|
EXPECT_EQ(token.type, TOKEN_WAVE_START);
|
|
EXPECT_EQ(token.fields.wave.cu, 5);
|
|
EXPECT_EQ(token.fields.wave.wave, 3);
|
|
EXPECT_EQ(token.fields.wave.simd, 2);
|
|
}
|
|
|
|
TEST(MITokenGeneratorTest, ParsesWaveEndToken)
|
|
{
|
|
// WAVE_END token is 16 bits
|
|
// Type 6 = TOKEN_WAVE_END
|
|
std::vector<uint8_t> buffer(2, 0);
|
|
|
|
uint16_t token_val = TOKEN_WAVE_END; // type = 6
|
|
token_val |= (0 << 4); // delta
|
|
token_val |= (1 << 5); // sh = 1
|
|
token_val |= (7 << 6); // cu = 7
|
|
token_val |= (5 << 10); // wave = 5
|
|
token_val |= (1 << 14); // simd = 1
|
|
|
|
buffer[0] = token_val & 0xFF;
|
|
buffer[1] = (token_val >> 8) & 0xFF;
|
|
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
auto token = gen.next();
|
|
EXPECT_EQ(token.type, TOKEN_WAVE_END);
|
|
EXPECT_EQ(token.fields.wave.cu, 7);
|
|
EXPECT_EQ(token.fields.wave.wave, 5);
|
|
EXPECT_EQ(token.fields.wave.simd, 1);
|
|
EXPECT_EQ(token.fields.wave.sh, 1);
|
|
}
|
|
|
|
TEST(MITokenGeneratorTest, ParsesInstToken)
|
|
{
|
|
// INST token is 16 bits
|
|
// Type 10 = TOKEN_INST
|
|
std::vector<uint8_t> buffer(2, 0);
|
|
|
|
uint16_t token_val = TOKEN_INST; // type = 10 (0xA)
|
|
token_val |= (1 << 4); // delta = 1
|
|
token_val |= (7 << 5); // wave = 7
|
|
token_val |= (2 << 9); // simd = 2
|
|
token_val |= (15 << 11); // inst_type = 15
|
|
|
|
buffer[0] = token_val & 0xFF;
|
|
buffer[1] = (token_val >> 8) & 0xFF;
|
|
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
auto token = gen.next();
|
|
EXPECT_EQ(token.type, TOKEN_INST);
|
|
EXPECT_EQ(token.fields.inst.wave, 7);
|
|
EXPECT_EQ(token.fields.inst.simd, 2);
|
|
EXPECT_EQ(token.fields.inst.inst_type, 15);
|
|
}
|
|
|
|
TEST(MITokenGeneratorTest, ParsesMiscToken)
|
|
{
|
|
// MISC token is 16 bits
|
|
// Type 0 = TOKEN_MISC
|
|
std::vector<uint8_t> buffer(2, 0);
|
|
|
|
uint16_t token_val = TOKEN_MISC; // type = 0
|
|
token_val |= (0x5A << 4); // delta (8 bits for MISC)
|
|
token_val |= (1 << 12); // sh = 1
|
|
token_val |= (3 << 13); // misc_type = 3
|
|
|
|
buffer[0] = token_val & 0xFF;
|
|
buffer[1] = (token_val >> 8) & 0xFF;
|
|
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
auto token = gen.next();
|
|
EXPECT_EQ(token.type, TOKEN_MISC);
|
|
EXPECT_EQ(token.fields.misc.sh, 1);
|
|
EXPECT_EQ(token.fields.misc.misc_type, 3);
|
|
EXPECT_EQ(token.delta, 0x5A);
|
|
}
|
|
|
|
TEST(MITokenGeneratorTest, MultipleTokensInSequence)
|
|
{
|
|
// Create buffer with multiple tokens
|
|
// gfx9 uses byte-aligned tokens, not bit-packed like gfx10+
|
|
// Token lengths: WAVE_START=32bits, WAVE_END=16bits
|
|
std::vector<uint8_t> buffer;
|
|
|
|
// Token 1: WAVE_START (32 bits = 4 bytes)
|
|
// type(0-3)=3, delta(4)=0, sh(5)=0, cu(6-9)=2, wave(10-13)=1, simd(14-15)=0
|
|
// pipe(16-17)=0, me(18-19)=0, count(22-28)=0
|
|
uint32_t ws_val = TOKEN_WAVE_START; // type = 3
|
|
ws_val |= (0 << 4); // delta
|
|
ws_val |= (0 << 5); // sh
|
|
ws_val |= (2 << 6); // cu = 2
|
|
ws_val |= (1 << 10); // wave = 1
|
|
ws_val |= (0 << 14); // simd = 0
|
|
ws_val |= (0 << 16); // pipe = 0
|
|
ws_val |= (0 << 18); // me = 0
|
|
ws_val |= (10 << 22); // count = 10
|
|
buffer.push_back(ws_val & 0xFF);
|
|
buffer.push_back((ws_val >> 8) & 0xFF);
|
|
buffer.push_back((ws_val >> 16) & 0xFF);
|
|
buffer.push_back((ws_val >> 24) & 0xFF);
|
|
|
|
// Token 2: WAVE_END (16 bits = 2 bytes)
|
|
// type(0-3)=6, delta(4)=0, sh(5)=0, cu(6-9)=2, wave(10-13)=1, simd(14-15)=0
|
|
uint16_t we_val = TOKEN_WAVE_END; // type = 6
|
|
we_val |= (0 << 4); // delta
|
|
we_val |= (0 << 5); // sh
|
|
we_val |= (2 << 6); // cu = 2
|
|
we_val |= (1 << 10); // wave = 1
|
|
we_val |= (0 << 14); // simd = 0
|
|
buffer.push_back(we_val & 0xFF);
|
|
buffer.push_back((we_val >> 8) & 0xFF);
|
|
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
// Token 1: WAVE_START
|
|
auto token1 = gen.next();
|
|
EXPECT_EQ(token1.type, TOKEN_WAVE_START);
|
|
EXPECT_EQ(token1.fields.wave.cu, 2);
|
|
EXPECT_EQ(token1.fields.wave.wave, 1);
|
|
EXPECT_EQ(token1.fields.wave.count, 10);
|
|
|
|
// Token 2: WAVE_END
|
|
auto token2 = gen.next();
|
|
EXPECT_EQ(token2.type, TOKEN_WAVE_END);
|
|
EXPECT_EQ(token2.fields.wave.cu, 2);
|
|
EXPECT_EQ(token2.fields.wave.wave, 1);
|
|
}
|
|
|
|
TEST(MITokenGeneratorTest, LargeBufferProcessesWithoutOob)
|
|
{
|
|
// Create a large buffer with valid MISC tokens (type 0, 16 bits each)
|
|
std::vector<uint8_t> buffer(256, 0);
|
|
// Fill with MISC tokens (type 0)
|
|
for (size_t i = 0; i < buffer.size(); i += 2)
|
|
{
|
|
buffer[i] = TOKEN_MISC; // type = 0
|
|
buffer[i + 1] = 0;
|
|
}
|
|
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
int tokenCount = 0;
|
|
while (gen.valid() && tokenCount < 1000)
|
|
{
|
|
auto token = gen.next();
|
|
tokenCount++;
|
|
EXPECT_EQ(token.type, TOKEN_MISC);
|
|
}
|
|
EXPECT_GT(tokenCount, 0);
|
|
EXPECT_FALSE(gen.valid());
|
|
}
|
|
|
|
// Tests for parsing multiple tokens via MITokenGenerator
|
|
TEST(Gfx9TokenParseTest, ParsesMultipleTokens)
|
|
{
|
|
std::vector<uint8_t> buffer;
|
|
|
|
// Add a MISC token (16 bits = 2 bytes), misc_type != 1 so it is returned
|
|
uint16_t misc_val = TOKEN_MISC;
|
|
misc_val |= (1 << 12); // sh = 1
|
|
misc_val |= (2 << 13); // misc_type = 2
|
|
buffer.push_back(misc_val & 0xFF);
|
|
buffer.push_back((misc_val >> 8) & 0xFF);
|
|
|
|
// Add a WAVE_START token (32 bits = 4 bytes per token_len_dict[3])
|
|
uint32_t ws_val = TOKEN_WAVE_START;
|
|
ws_val |= (3 << 6); // cu = 3
|
|
ws_val |= (2 << 10); // wave = 2
|
|
ws_val |= (1 << 14); // simd = 1
|
|
buffer.push_back(ws_val & 0xFF);
|
|
buffer.push_back((ws_val >> 8) & 0xFF);
|
|
buffer.push_back((ws_val >> 16) & 0xFF);
|
|
buffer.push_back((ws_val >> 24) & 0xFF);
|
|
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
auto token0 = gen.next();
|
|
EXPECT_EQ(token0.type, TOKEN_MISC);
|
|
|
|
auto token1 = gen.next();
|
|
EXPECT_EQ(token1.type, TOKEN_WAVE_START);
|
|
EXPECT_EQ(token1.fields.wave.cu, 3);
|
|
EXPECT_EQ(token1.fields.wave.wave, 2);
|
|
}
|
|
|
|
// Edge case tests for field boundaries
|
|
TEST(Gfx9WaveEdgeCaseTest, MaxFieldValues)
|
|
{
|
|
// Max values for wave fields
|
|
// cu(6-9)=15, sh(5)=1, wave(10-13)=9 (max allowed before CHECK_WAVE throws), simd(14-15)=3
|
|
uint64_t val = 0;
|
|
val |= (1ull << 5); // sh = 1
|
|
val |= (15ull << 6); // cu = 15 (max 4-bit)
|
|
val |= (9ull << 10); // wave = 9 (max before exception)
|
|
val |= (3ull << 14); // simd = 3 (max 2-bit)
|
|
val |= (3ull << 16); // pipe = 3 (max 2-bit)
|
|
val |= (3ull << 18); // me = 3 (max 2-bit)
|
|
val |= (127ull << 22); // count = 127 (max 7-bit)
|
|
|
|
Wave wave(val);
|
|
|
|
EXPECT_EQ(wave.cu, 15);
|
|
EXPECT_EQ(wave.sh, 1);
|
|
EXPECT_EQ(wave.wave, 9);
|
|
EXPECT_EQ(wave.simd, 3);
|
|
EXPECT_EQ(wave.pipe, 3);
|
|
EXPECT_EQ(wave.count, 127);
|
|
}
|
|
|
|
TEST(Gfx9WaveEdgeCaseTest, WaveValueAbove9Throws)
|
|
{
|
|
// wave = 10 should throw due to CHECK_WAVE()
|
|
uint64_t val = 0;
|
|
val |= (10ull << 10); // wave = 10 (exceeds max of 9)
|
|
|
|
EXPECT_THROW(Wave wave(val), std::exception);
|
|
}
|
|
|
|
TEST(Gfx9MsgInstEdgeCaseTest, MaxFieldValues)
|
|
{
|
|
// wave(5-8)=9, simd(9-10)=3, inst_type(11-15)=31
|
|
uint64_t val = 0;
|
|
val |= (9ull << 5); // wave = 9 (max before exception)
|
|
val |= (3ull << 9); // simd = 3
|
|
val |= (31ull << 11); // inst_type = 31 (max 5-bit)
|
|
|
|
MsgInst inst(val);
|
|
|
|
EXPECT_EQ(inst.wave, 9);
|
|
EXPECT_EQ(inst.simd, 3);
|
|
EXPECT_EQ(inst.inst_type, 31);
|
|
}
|
|
|
|
TEST(Gfx9MsgInstPcEdgeCaseTest, MaxPcValue)
|
|
{
|
|
// Test with large PC value
|
|
uint64_t val = 0;
|
|
val |= (5ull << 5); // wave = 5
|
|
val |= (2ull << 9); // simd = 2
|
|
val |= (1ull << 15); // err = 1
|
|
val |= (0xFFFFFFFFFFFFull << 16); // pc = max 48-bit value
|
|
|
|
MsgInstPc instpc(val);
|
|
|
|
EXPECT_EQ(instpc.wave, 5);
|
|
EXPECT_EQ(instpc.err, 1);
|
|
EXPECT_EQ(instpc.pc, 0xFFFFFFFFFFFFull);
|
|
}
|
|
|
|
TEST(Gfx9IssueEdgeCaseTest, AllInstSlotsFilled)
|
|
{
|
|
// Fill all 10 inst slots with max value (3)
|
|
uint64_t val = 0;
|
|
val |= (3ull << 5); // simd = 3
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
val |= (3ull << (8 + i * 2)); // inst[i] = 3
|
|
}
|
|
|
|
Issue issue(val);
|
|
|
|
EXPECT_EQ(issue.simd, 3);
|
|
for (int i = 0; i < 10; i++) { EXPECT_EQ(issue.inst[i], 3); }
|
|
}
|
|
|
|
// Test that parseOne does not read out of bounds when a token near the end
|
|
// of the buffer requires more bytes than are available.
|
|
TEST(Gfx9OobTest, TruncatedTokenDoesNotReadPastBuffer)
|
|
{
|
|
// Build a 6-byte buffer:
|
|
// bytes 0-1: WAVE_END token (type 6, 16-bit → 2 bytes, valid)
|
|
// bytes 2-5: TIME header (type 1 needs 64 bits = 8 bytes, but only 4 remain)
|
|
std::vector<uint8_t> buffer(6, 0);
|
|
|
|
// WAVE_END (type 6) with wave=3, cu=1, simd=0, sh=0
|
|
uint16_t we_val = TOKEN_WAVE_END;
|
|
we_val |= (1 << 6); // cu = 1
|
|
we_val |= (3 << 10); // wave = 3
|
|
buffer[0] = we_val & 0xFF;
|
|
buffer[1] = (we_val >> 8) & 0xFF;
|
|
|
|
// TIME token header byte (type 1) — needs 8 bytes but only 4 remain
|
|
buffer[2] = TOKEN_TIME;
|
|
buffer[3] = 0;
|
|
buffer[4] = 0;
|
|
buffer[5] = 0;
|
|
|
|
MITokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
|
|
|
|
// First next() should return the valid WAVE_END token
|
|
auto token = gen.next();
|
|
EXPECT_EQ(token.type, TOKEN_WAVE_END);
|
|
EXPECT_EQ(token.fields.wave.cu, 1);
|
|
EXPECT_EQ(token.fields.wave.wave, 3);
|
|
|
|
// Second next() encounters the truncated TIME token. The safe path
|
|
// detects there are only 4 bytes remaining (needs 8) and terminates
|
|
// without reading past the buffer. Under ASAN this would flag a
|
|
// heap-buffer-overflow if bounds were not checked.
|
|
EXPECT_TRUE(gen.valid()); // bytes still in buffer
|
|
auto sentinel = gen.next();
|
|
EXPECT_EQ(sentinel.type, TOKEN_TIME); // sentinel / consumed
|
|
EXPECT_FALSE(gen.valid());
|
|
}
|
|
|
|
// Tests for print_token
|
|
TEST(Gfx9PrintTokenTest, MiscWaveStartOther)
|
|
{
|
|
// MISC with PACKET_LOST
|
|
uint64_t misc_val = (2ull << 13);
|
|
Token misc_tok(0, misc_val);
|
|
misc_tok.type = TOKEN_MISC;
|
|
auto misc_str = print_token(misc_tok);
|
|
EXPECT_NE(misc_str.find("TOKEN_MISC"), std::string::npos);
|
|
EXPECT_NE(misc_str.find("PACKET_LOST"), std::string::npos);
|
|
|
|
// WAVE_START
|
|
Token ws_tok(TOKEN_WAVE_START, (3ull << 6) | (1ull << 5));
|
|
ws_tok.type = TOKEN_WAVE_START;
|
|
auto ws_str = print_token(ws_tok);
|
|
EXPECT_NE(ws_str.find("TOKEN_WAVE_START"), std::string::npos);
|
|
EXPECT_NE(ws_str.find("start:"), std::string::npos);
|
|
|
|
// Other token (INST) - no special fields
|
|
Token inst_tok(TOKEN_INST, 0);
|
|
inst_tok.type = TOKEN_INST;
|
|
auto inst_str = print_token(inst_tok);
|
|
EXPECT_NE(inst_str.find("TOKEN_INST"), std::string::npos);
|
|
EXPECT_EQ(inst_str.find("misc:"), std::string::npos);
|
|
}
|
|
|
|
// Tests for CSRegisterHandlerGFX9::HandleRealtimeClock
|
|
TEST(Gfx9CSRegTest, HandleRealtimeClockNotROCM)
|
|
{
|
|
CSRegisterHandlerGFX9 csreg;
|
|
csreg.bIsROCMFormat = false;
|
|
csreg.HandleRealtimeClock(1000, 0x5);
|
|
EXPECT_TRUE(csreg.realtime.empty());
|
|
}
|
|
|
|
TEST(Gfx9CSRegTest, HandleRealtimeClockFullSequence)
|
|
{
|
|
CSRegisterHandlerGFX9 csreg;
|
|
csreg.bIsROCMFormat = true;
|
|
|
|
csreg.HandleRealtimeClock(1000, 0x5); // init
|
|
EXPECT_TRUE(csreg.realtime.empty());
|
|
csreg.HandleRealtimeClock(1004, 0xAABBCCDD); // RT_LOW
|
|
csreg.HandleRealtimeClock(1008, 0x11223344); // RT_HI
|
|
csreg.HandleRealtimeClock(1012, 0xAABBCCFF); // RT_DELTA > RT_LOW => no wrap
|
|
ASSERT_EQ(csreg.realtime.size(), 1u);
|
|
EXPECT_EQ(csreg.realtime[0].shader_clock, 1012);
|
|
}
|
|
|
|
TEST(Gfx9CSRegTest, HandleRealtimeClockWrapping)
|
|
{
|
|
CSRegisterHandlerGFX9 csreg;
|
|
csreg.bIsROCMFormat = true;
|
|
|
|
csreg.HandleRealtimeClock(1000, 0x5);
|
|
csreg.HandleRealtimeClock(1004, 0xFFFFFFF0); // RT_LOW
|
|
csreg.HandleRealtimeClock(1008, 0x00000001); // RT_HI
|
|
csreg.HandleRealtimeClock(1012, 0x00000010); // RT_DELTA < RT_LOW => wrap
|
|
ASSERT_EQ(csreg.realtime.size(), 1u);
|
|
uint64_t expected = 0x00000010 | ((0x00000001ull << 32) + (1ull << 32));
|
|
EXPECT_EQ(csreg.realtime[0].realtime_clock, expected);
|
|
}
|
|
|
|
TEST(Gfx9CSRegTest, HandleRealtimeClockNonInitIgnored)
|
|
{
|
|
CSRegisterHandlerGFX9 csreg;
|
|
csreg.bIsROCMFormat = true;
|
|
csreg.HandleRealtimeClock(1000, 0x3); // data != 0x5 when count==0
|
|
EXPECT_TRUE(csreg.realtime.empty());
|
|
}
|
|
|
|
// Tests for apply_issue
|
|
TEST(Gfx9WaveTest, ApplyIssueAllPaths)
|
|
{
|
|
auto make_wave = []()
|
|
{
|
|
Wave ws(0);
|
|
ws.cu = 0;
|
|
ws.simd = 0;
|
|
ws.wave = 0;
|
|
return wave_t(0, pcinfo_t{100, 1}, ws, 0);
|
|
};
|
|
|
|
// IMMED
|
|
{
|
|
auto w = make_wave();
|
|
w.instructions.push_back(Instruction(0, WaveInstCategory::VALU, 4, 0));
|
|
EXPECT_EQ(w.apply_issue(3, 100), 0);
|
|
ASSERT_GE(w.instructions.size(), 2u);
|
|
EXPECT_EQ(w.instructions.back().category, WaveInstCategory::IMMED);
|
|
}
|
|
// STALL
|
|
{
|
|
auto w = make_wave();
|
|
EXPECT_EQ(w.apply_issue(1, 100), 0);
|
|
EXPECT_EQ(w.stall_start_times.back(), 100);
|
|
}
|
|
// INST
|
|
{
|
|
auto w = make_wave();
|
|
EXPECT_EQ(w.apply_issue(2, 100), 1);
|
|
EXPECT_EQ(w.instructions.back().category, WaveInstCategory::WAVE_NOT_FINISHED);
|
|
}
|
|
// Not restored
|
|
{
|
|
auto w = make_wave();
|
|
w.trap_status = WaveTrapStatus::TRAP_STANDBY;
|
|
EXPECT_EQ(w.apply_issue(2, 100), 0);
|
|
EXPECT_TRUE(w.instructions.empty());
|
|
}
|
|
}
|
|
|
|
// Tests for apply_inst type dispatch
|
|
class Gfx9ApplyInstTest : public ::testing::Test
|
|
{
|
|
protected:
|
|
wave_t make_wave()
|
|
{
|
|
Wave ws(0);
|
|
ws.cu = 0;
|
|
ws.simd = 0;
|
|
ws.wave = 0;
|
|
wave_t w(0, pcinfo_t{100, 1}, ws, 0);
|
|
w.apply_issue(2, 100);
|
|
return w;
|
|
}
|
|
|
|
unsigned int apply_type(int inst_type)
|
|
{
|
|
auto w = make_wave();
|
|
Token tok(TOKEN_INST, 0);
|
|
tok.type = TOKEN_INST;
|
|
tok.time = 104;
|
|
tok.fields.inst.inst_type = inst_type;
|
|
tok.fields.inst.simd = 0;
|
|
tok.fields.inst.wave = 0;
|
|
int64_t phase = 15;
|
|
w.apply_inst(tok, phase);
|
|
for (auto& i : w.instructions)
|
|
if (i.category != WaveInstCategory::WAVE_NOT_FINISHED) return i.category;
|
|
return WaveInstCategory::NONE;
|
|
}
|
|
};
|
|
|
|
TEST_F(Gfx9ApplyInstTest, AllInstTypes)
|
|
{
|
|
EXPECT_EQ(apply_type(0), WaveInstCategory::SMEM); // SMEM_RD
|
|
EXPECT_EQ(apply_type(1), WaveInstCategory::SALU); // SALU_32
|
|
EXPECT_EQ(apply_type(2), WaveInstCategory::VMEM); // VMEM_RD
|
|
EXPECT_EQ(apply_type(5), WaveInstCategory::VALU); // VALU_32
|
|
EXPECT_EQ(apply_type(6), WaveInstCategory::LDS); // LDS
|
|
EXPECT_EQ(apply_type(12), WaveInstCategory::JUMP); // JUMP
|
|
EXPECT_EQ(apply_type(13), WaveInstCategory::NEXT); // NEXT
|
|
EXPECT_EQ(apply_type(14), WaveInstCategory::FLAT); // FLAT_RD
|
|
EXPECT_EQ(apply_type(15), WaveInstCategory::MSG); // OTHER_MSG
|
|
EXPECT_EQ(apply_type(18), WaveInstCategory::VALU); // VALU_64
|
|
}
|
|
|
|
TEST_F(Gfx9ApplyInstTest, NotRestoredReturnsEarly)
|
|
{
|
|
auto w = make_wave();
|
|
w.trap_status = WaveTrapStatus::TRAP_STANDBY;
|
|
w.instructions.clear();
|
|
Token tok(TOKEN_INST, 0);
|
|
tok.type = TOKEN_INST;
|
|
tok.time = 104;
|
|
tok.fields.inst.inst_type = 1;
|
|
int64_t phase = 15;
|
|
w.apply_inst(tok, phase);
|
|
EXPECT_TRUE(w.instructions.empty());
|
|
}
|
|
|
|
TEST_F(Gfx9ApplyInstTest, GoToTrapAndPCRestore)
|
|
{
|
|
auto w = make_wave();
|
|
|
|
// GO_TO_TRAP
|
|
Token trap_tok(TOKEN_INST, 0);
|
|
trap_tok.type = TOKEN_INST;
|
|
trap_tok.time = 104;
|
|
trap_tok.fields.inst.inst_type = 29;
|
|
int64_t phase = 15;
|
|
w.apply_inst(trap_tok, phase);
|
|
EXPECT_EQ(w.trap_status, WaveTrapStatus::TRAP_WAIT_FOR_NEW_PC);
|
|
|
|
// Issue + PC type restores
|
|
w.apply_issue(2, 200);
|
|
Token pc_tok(TOKEN_INST, 0);
|
|
pc_tok.type = TOKEN_INST;
|
|
pc_tok.time = 204;
|
|
pc_tok.fields.inst.inst_type = 7;
|
|
w.apply_inst(pc_tok, phase);
|
|
EXPECT_EQ(w.trap_status, WaveTrapStatus::TRAP_RESTORED);
|
|
}
|
|
|
|
TEST_F(Gfx9ApplyInstTest, StallApplied)
|
|
{
|
|
Wave ws(0);
|
|
ws.cu = 0;
|
|
ws.simd = 0;
|
|
ws.wave = 0;
|
|
wave_t w(0, pcinfo_t{100, 1}, ws, 0);
|
|
w.apply_issue(1, 50); // STALL
|
|
w.apply_issue(2, 100); // INST
|
|
Token tok(TOKEN_INST, 0);
|
|
tok.type = TOKEN_INST;
|
|
tok.time = 200;
|
|
tok.fields.inst.inst_type = 5;
|
|
int64_t phase = 15;
|
|
w.apply_inst(tok, phase);
|
|
bool found = false;
|
|
for (auto& i : w.instructions)
|
|
if (i.category == WaveInstCategory::VALU && i.stall > 0) found = true;
|
|
EXPECT_TRUE(found);
|
|
}
|
|
|
|
TEST_F(Gfx9ApplyInstTest, SmemRdReplay)
|
|
{
|
|
auto w = make_wave();
|
|
// First issue a SMEM_RD (inst_type=0) so last_inst_for_xnack[0] is set
|
|
Token smem_tok(TOKEN_INST, 0);
|
|
smem_tok.type = TOKEN_INST;
|
|
smem_tok.time = 104;
|
|
smem_tok.fields.inst.inst_type = 0; // SMEM_RD
|
|
smem_tok.fields.inst.simd = 0;
|
|
smem_tok.fields.inst.wave = 0;
|
|
int64_t phase = 15;
|
|
w.apply_inst(smem_tok, phase);
|
|
|
|
// Issue another instruction slot
|
|
w.apply_issue(2, 200);
|
|
|
|
// Now issue SMEM_RD_REPLAY (inst_type=19) to trigger the replay/xnack path
|
|
Token replay_tok(TOKEN_INST, 0);
|
|
replay_tok.type = TOKEN_INST;
|
|
replay_tok.time = 300;
|
|
replay_tok.fields.inst.inst_type = 19; // SMEM_RD_REPLAY
|
|
replay_tok.fields.inst.simd = 0;
|
|
replay_tok.fields.inst.wave = 0;
|
|
w.apply_inst(replay_tok, phase);
|
|
|
|
// The replay should erase the instruction (not add a new category)
|
|
bool found_replay_cat = false;
|
|
for (auto& i : w.instructions)
|
|
if (i.category == 19) found_replay_cat = true;
|
|
EXPECT_FALSE(found_replay_cat);
|
|
}
|
|
|
|
// Tests for convertPerfEventsTs
|
|
TEST(Gfx9PerfEventsTest, InsertsGapEvents)
|
|
{
|
|
std::vector<att_perfevent_t> vec;
|
|
att_perfevent_t ev1{};
|
|
ev1.time = 100;
|
|
ev1.CU = 0;
|
|
ev1.bank = 0;
|
|
att_perfevent_t ev2{};
|
|
ev2.time = 300;
|
|
ev2.CU = 0;
|
|
ev2.bank = 0;
|
|
vec.push_back(ev1);
|
|
vec.push_back(ev2);
|
|
convertPerfEventsTs(vec, 50);
|
|
EXPECT_GT(vec.size(), 2u);
|
|
}
|