mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-04 21:29:54 +01:00
v2.12.0
--- Improvements --- * Running tests in random order (`--order rand`) has been reworked significantly (#1908) * Given same seed, all platforms now produce the same order * Given same seed, the relative order of tests does not change if you select only a subset of them * Vector matchers support custom allocators (#1909) * `|` and `&` (bitwise or and bitwise and) are now supported in `CHECK` and `REQUIRE` * The resulting type must be convertible to `bool` --- Fixes --- * Fixed computation of benchmarking column widths in ConsoleReporter (#1885, #1886) * Suppressed clang-tidy's `cppcoreguidelines-pro-type-vararg` in assertions (#1901) * It was a false positive trigered by the new warning support workaround * Fixed bug in test specification parser handling of OR'd patterns using escaping (#1905) --- Miscellaneous --- * Worked around IBM XL's codegen bug (#1907) * It would emit code for _destructors_ of temporaries in an unevaluated context * Improved detection of stdlib's support for `std::uncaught_exceptions` (#1911)
This commit is contained in:
parent
e59fc2c3b3
commit
cfb6956698
@ -14,7 +14,7 @@ if (CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
endif()
|
||||
|
||||
|
||||
project(Catch2 LANGUAGES CXX VERSION 2.11.3)
|
||||
project(Catch2 LANGUAGES CXX VERSION 2.12.0)
|
||||
|
||||
# Provide path for scripts
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake")
|
||||
|
@ -5,11 +5,11 @@
|
||||
[![Build Status](https://travis-ci.org/catchorg/Catch2.svg?branch=master)](https://travis-ci.org/catchorg/Catch2)
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/github/catchorg/Catch2?svg=true)](https://ci.appveyor.com/project/catchorg/catch2)
|
||||
[![codecov](https://codecov.io/gh/catchorg/Catch2/branch/master/graph/badge.svg)](https://codecov.io/gh/catchorg/Catch2)
|
||||
[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/p9Pcgple8QWwgNR0)
|
||||
[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/oT7SOoJ9ua6JsdEB)
|
||||
[![Join the chat in Discord: https://discord.gg/4CWS9zD](https://img.shields.io/badge/Discord-Chat!-brightgreen.svg)](https://discord.gg/4CWS9zD)
|
||||
|
||||
|
||||
<a href="https://github.com/catchorg/Catch2/releases/download/v2.11.3/catch.hpp">The latest version of the single header can be downloaded directly using this link</a>
|
||||
<a href="https://github.com/catchorg/Catch2/releases/download/v2.12.0/catch.hpp">The latest version of the single header can be downloaded directly using this link</a>
|
||||
|
||||
## Catch2 is released!
|
||||
|
||||
|
@ -260,6 +260,8 @@ Randomly sorted. The order is dependent on Catch2's random seed (see
|
||||
is that as long as the random seed is fixed, running only some tests
|
||||
(e.g. via tag) does not change their relative order.
|
||||
|
||||
> The subset stability was introduced in Catch2 v2.12.0
|
||||
|
||||
|
||||
<a id="rng-seed"></a>
|
||||
## Specify a seed for the Random Number Generator
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
# Release notes
|
||||
**Contents**<br>
|
||||
[2.12.0](#2120)<br>
|
||||
[2.11.3](#2113)<br>
|
||||
[2.11.2](#2112)<br>
|
||||
[2.11.1](#2111)<br>
|
||||
@ -34,6 +35,28 @@
|
||||
[Older versions](#older-versions)<br>
|
||||
[Even Older versions](#even-older-versions)<br>
|
||||
|
||||
## 2.12.0
|
||||
|
||||
### Improvements
|
||||
* Running tests in random order (`--order rand`) has been reworked significantly (#1908)
|
||||
* Given same seed, all platforms now produce the same order
|
||||
* Given same seed, the relative order of tests does not change if you select only a subset of them
|
||||
* Vector matchers support custom allocators (#1909)
|
||||
* `|` and `&` (bitwise or and bitwise and) are now supported in `CHECK` and `REQUIRE`
|
||||
* The resulting type must be convertible to `bool`
|
||||
|
||||
### Fixes
|
||||
* Fixed computation of benchmarking column widths in ConsoleReporter (#1885, #1886)
|
||||
* Suppressed clang-tidy's `cppcoreguidelines-pro-type-vararg` in assertions (#1901)
|
||||
* It was a false positive trigered by the new warning support workaround
|
||||
* Fixed bug in test specification parser handling of OR'd patterns using escaping (#1905)
|
||||
|
||||
### Miscellaneous
|
||||
* Worked around IBM XL's codegen bug (#1907)
|
||||
* It would emit code for _destructors_ of temporaries in an unevaluated context
|
||||
* Improved detection of stdlib's support for `std::uncaught_exceptions` (#1911)
|
||||
|
||||
|
||||
## 2.11.3
|
||||
|
||||
### Fixes
|
||||
|
@ -10,8 +10,8 @@
|
||||
#define TWOBLUECUBES_CATCH_HPP_INCLUDED
|
||||
|
||||
#define CATCH_VERSION_MAJOR 2
|
||||
#define CATCH_VERSION_MINOR 11
|
||||
#define CATCH_VERSION_PATCH 3
|
||||
#define CATCH_VERSION_MINOR 12
|
||||
#define CATCH_VERSION_PATCH 0
|
||||
|
||||
#ifdef __clang__
|
||||
# pragma clang system_header
|
||||
|
@ -37,7 +37,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 2, 11, 3, "", 0 );
|
||||
static Version version( 2, 12, 0, "", 0 );
|
||||
return version;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Catch v2.11.3
|
||||
* Generated: 2020-03-19 13:44:21.042491
|
||||
* Catch v2.12.0
|
||||
* Generated: 2020-04-21 16:27:54.138031
|
||||
* ----------------------------------------------------------
|
||||
* This file has been merged from multiple headers. Please don't edit it directly
|
||||
* Copyright (c) 2020 Two Blue Cubes Ltd. All rights reserved.
|
||||
@ -14,8 +14,8 @@
|
||||
|
||||
|
||||
#define CATCH_VERSION_MAJOR 2
|
||||
#define CATCH_VERSION_MINOR 11
|
||||
#define CATCH_VERSION_PATCH 3
|
||||
#define CATCH_VERSION_MINOR 12
|
||||
#define CATCH_VERSION_PATCH 0
|
||||
|
||||
#ifdef __clang__
|
||||
# pragma clang system_header
|
||||
@ -132,7 +132,7 @@ namespace Catch {
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(CATCH_CPP17_OR_GREATER)
|
||||
#if defined(__cpp_lib_uncaught_exceptions)
|
||||
# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
|
||||
#endif
|
||||
|
||||
@ -151,7 +151,20 @@ namespace Catch {
|
||||
# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" )
|
||||
# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" )
|
||||
|
||||
# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__)
|
||||
// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug
|
||||
// which results in calls to destructors being emitted for each temporary,
|
||||
// without a matching initialization. In practice, this can result in something
|
||||
// like `std::string::~string` being called on an uninitialized value.
|
||||
//
|
||||
// For example, this code will likely segfault under IBM XL:
|
||||
// ```
|
||||
// REQUIRE(std::string("12") + "34" == "1234")
|
||||
// ```
|
||||
//
|
||||
// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented.
|
||||
# if !defined(__ibmxl__)
|
||||
# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg) */
|
||||
# endif
|
||||
|
||||
# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
|
||||
_Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \
|
||||
@ -2356,6 +2369,14 @@ namespace Catch {
|
||||
auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
|
||||
return { static_cast<bool>(m_lhs <= rhs), m_lhs, "<=", rhs };
|
||||
}
|
||||
template <typename RhsT>
|
||||
auto operator | (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const {
|
||||
return { static_cast<bool>(m_lhs | rhs), m_lhs, "|", rhs };
|
||||
}
|
||||
template <typename RhsT>
|
||||
auto operator & (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const {
|
||||
return { static_cast<bool>(m_lhs & rhs), m_lhs, "&", rhs };
|
||||
}
|
||||
|
||||
template<typename RhsT>
|
||||
auto operator && ( RhsT const& ) -> BinaryExpr<LhsT, RhsT const&> const {
|
||||
@ -3000,6 +3021,9 @@ namespace Catch {
|
||||
{}
|
||||
|
||||
std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override {
|
||||
#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
|
||||
return "";
|
||||
#else
|
||||
try {
|
||||
if( it == itEnd )
|
||||
std::rethrow_exception(std::current_exception());
|
||||
@ -3009,6 +3033,7 @@ namespace Catch {
|
||||
catch( T& ex ) {
|
||||
return m_translateFunction( ex );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
@ -3571,12 +3596,12 @@ namespace Catch {
|
||||
namespace Matchers {
|
||||
|
||||
namespace Vector {
|
||||
template<typename T>
|
||||
struct ContainsElementMatcher : MatcherBase<std::vector<T>> {
|
||||
template<typename T, typename Alloc>
|
||||
struct ContainsElementMatcher : MatcherBase<std::vector<T, Alloc>> {
|
||||
|
||||
ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {}
|
||||
|
||||
bool match(std::vector<T> const &v) const override {
|
||||
bool match(std::vector<T, Alloc> const &v) const override {
|
||||
for (auto const& el : v) {
|
||||
if (el == m_comparator) {
|
||||
return true;
|
||||
@ -3592,12 +3617,12 @@ namespace Matchers {
|
||||
T const& m_comparator;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ContainsMatcher : MatcherBase<std::vector<T>> {
|
||||
template<typename T, typename AllocComp, typename AllocMatch>
|
||||
struct ContainsMatcher : MatcherBase<std::vector<T, AllocMatch>> {
|
||||
|
||||
ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {}
|
||||
ContainsMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator( comparator ) {}
|
||||
|
||||
bool match(std::vector<T> const &v) const override {
|
||||
bool match(std::vector<T, AllocMatch> const &v) const override {
|
||||
// !TBD: see note in EqualsMatcher
|
||||
if (m_comparator.size() > v.size())
|
||||
return false;
|
||||
@ -3619,18 +3644,18 @@ namespace Matchers {
|
||||
return "Contains: " + ::Catch::Detail::stringify( m_comparator );
|
||||
}
|
||||
|
||||
std::vector<T> const& m_comparator;
|
||||
std::vector<T, AllocComp> const& m_comparator;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct EqualsMatcher : MatcherBase<std::vector<T>> {
|
||||
template<typename T, typename AllocComp, typename AllocMatch>
|
||||
struct EqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> {
|
||||
|
||||
EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {}
|
||||
EqualsMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator( comparator ) {}
|
||||
|
||||
bool match(std::vector<T> const &v) const override {
|
||||
bool match(std::vector<T, AllocMatch> const &v) const override {
|
||||
// !TBD: This currently works if all elements can be compared using !=
|
||||
// - a more general approach would be via a compare template that defaults
|
||||
// to using !=. but could be specialised for, e.g. std::vector<T> etc
|
||||
// to using !=. but could be specialised for, e.g. std::vector<T, Alloc> etc
|
||||
// - then just call that directly
|
||||
if (m_comparator.size() != v.size())
|
||||
return false;
|
||||
@ -3642,15 +3667,15 @@ namespace Matchers {
|
||||
std::string describe() const override {
|
||||
return "Equals: " + ::Catch::Detail::stringify( m_comparator );
|
||||
}
|
||||
std::vector<T> const& m_comparator;
|
||||
std::vector<T, AllocComp> const& m_comparator;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ApproxMatcher : MatcherBase<std::vector<T>> {
|
||||
template<typename T, typename AllocComp, typename AllocMatch>
|
||||
struct ApproxMatcher : MatcherBase<std::vector<T, AllocMatch>> {
|
||||
|
||||
ApproxMatcher(std::vector<T> const& comparator) : m_comparator( comparator ) {}
|
||||
ApproxMatcher(std::vector<T, AllocComp> const& comparator) : m_comparator( comparator ) {}
|
||||
|
||||
bool match(std::vector<T> const &v) const override {
|
||||
bool match(std::vector<T, AllocMatch> const &v) const override {
|
||||
if (m_comparator.size() != v.size())
|
||||
return false;
|
||||
for (std::size_t i = 0; i < v.size(); ++i)
|
||||
@ -3677,14 +3702,14 @@ namespace Matchers {
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<T> const& m_comparator;
|
||||
std::vector<T, AllocComp> const& m_comparator;
|
||||
mutable Catch::Detail::Approx approx = Catch::Detail::Approx::custom();
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct UnorderedEqualsMatcher : MatcherBase<std::vector<T>> {
|
||||
UnorderedEqualsMatcher(std::vector<T> const& target) : m_target(target) {}
|
||||
bool match(std::vector<T> const& vec) const override {
|
||||
template<typename T, typename AllocComp, typename AllocMatch>
|
||||
struct UnorderedEqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> {
|
||||
UnorderedEqualsMatcher(std::vector<T, AllocComp> const& target) : m_target(target) {}
|
||||
bool match(std::vector<T, AllocMatch> const& vec) const override {
|
||||
// Note: This is a reimplementation of std::is_permutation,
|
||||
// because I don't want to include <algorithm> inside the common path
|
||||
if (m_target.size() != vec.size()) {
|
||||
@ -3697,7 +3722,7 @@ namespace Matchers {
|
||||
return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target);
|
||||
}
|
||||
private:
|
||||
std::vector<T> const& m_target;
|
||||
std::vector<T, AllocComp> const& m_target;
|
||||
};
|
||||
|
||||
} // namespace Vector
|
||||
@ -3705,29 +3730,29 @@ namespace Matchers {
|
||||
// The following functions create the actual matcher objects.
|
||||
// This allows the types to be inferred
|
||||
|
||||
template<typename T>
|
||||
Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) {
|
||||
return Vector::ContainsMatcher<T>( comparator );
|
||||
template<typename T, typename AllocComp, typename AllocMatch = AllocComp>
|
||||
Vector::ContainsMatcher<T, AllocComp, AllocMatch> Contains( std::vector<T, AllocComp> const& comparator ) {
|
||||
return Vector::ContainsMatcher<T, AllocComp, AllocMatch>( comparator );
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) {
|
||||
return Vector::ContainsElementMatcher<T>( comparator );
|
||||
template<typename T, typename Alloc = std::allocator<T>>
|
||||
Vector::ContainsElementMatcher<T, Alloc> VectorContains( T const& comparator ) {
|
||||
return Vector::ContainsElementMatcher<T, Alloc>( comparator );
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) {
|
||||
return Vector::EqualsMatcher<T>( comparator );
|
||||
template<typename T, typename AllocComp, typename AllocMatch = AllocComp>
|
||||
Vector::EqualsMatcher<T, AllocComp, AllocMatch> Equals( std::vector<T, AllocComp> const& comparator ) {
|
||||
return Vector::EqualsMatcher<T, AllocComp, AllocMatch>( comparator );
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Vector::ApproxMatcher<T> Approx( std::vector<T> const& comparator ) {
|
||||
return Vector::ApproxMatcher<T>( comparator );
|
||||
template<typename T, typename AllocComp, typename AllocMatch = AllocComp>
|
||||
Vector::ApproxMatcher<T, AllocComp, AllocMatch> Approx( std::vector<T, AllocComp> const& comparator ) {
|
||||
return Vector::ApproxMatcher<T, AllocComp, AllocMatch>( comparator );
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Vector::UnorderedEqualsMatcher<T> UnorderedEquals(std::vector<T> const& target) {
|
||||
return Vector::UnorderedEqualsMatcher<T>(target);
|
||||
template<typename T, typename AllocComp, typename AllocMatch = AllocComp>
|
||||
Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch> UnorderedEquals(std::vector<T, AllocComp> const& target) {
|
||||
return Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch>( target );
|
||||
}
|
||||
|
||||
} // namespace Matchers
|
||||
@ -10319,8 +10344,7 @@ namespace Catch {
|
||||
|
||||
#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE)
|
||||
|
||||
# include <assert.h>
|
||||
# include <stdbool.h>
|
||||
# include <cassert>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
# include <cstddef>
|
||||
@ -13977,27 +14001,77 @@ namespace Catch {
|
||||
// end catch_test_case_info.cpp
|
||||
// start catch_test_case_registry_impl.cpp
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
namespace {
|
||||
struct TestHasher {
|
||||
explicit TestHasher(Catch::SimplePcg32& rng) {
|
||||
basis = rng();
|
||||
basis <<= 32;
|
||||
basis |= rng();
|
||||
}
|
||||
|
||||
uint64_t basis;
|
||||
|
||||
uint64_t operator()(TestCase const& t) const {
|
||||
// Modified FNV-1a hash
|
||||
static constexpr uint64_t prime = 1099511628211;
|
||||
uint64_t hash = basis;
|
||||
for (const char c : t.name) {
|
||||
hash ^= c;
|
||||
hash *= prime;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
} // end unnamed namespace
|
||||
|
||||
std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) {
|
||||
|
||||
std::vector<TestCase> sorted = unsortedTestCases;
|
||||
|
||||
switch( config.runOrder() ) {
|
||||
case RunTests::InLexicographicalOrder:
|
||||
std::sort( sorted.begin(), sorted.end() );
|
||||
break;
|
||||
case RunTests::InRandomOrder:
|
||||
seedRng( config );
|
||||
std::shuffle( sorted.begin(), sorted.end(), rng() );
|
||||
break;
|
||||
case RunTests::InDeclarationOrder:
|
||||
// already in declaration order
|
||||
break;
|
||||
|
||||
case RunTests::InLexicographicalOrder: {
|
||||
std::vector<TestCase> sorted = unsortedTestCases;
|
||||
std::sort( sorted.begin(), sorted.end() );
|
||||
return sorted;
|
||||
}
|
||||
|
||||
case RunTests::InRandomOrder: {
|
||||
seedRng( config );
|
||||
TestHasher h( rng() );
|
||||
|
||||
using hashedTest = std::pair<uint64_t, TestCase const*>;
|
||||
std::vector<hashedTest> indexed_tests;
|
||||
indexed_tests.reserve( unsortedTestCases.size() );
|
||||
|
||||
for (auto const& testCase : unsortedTestCases) {
|
||||
indexed_tests.emplace_back(h(testCase), &testCase);
|
||||
}
|
||||
|
||||
std::sort(indexed_tests.begin(), indexed_tests.end(),
|
||||
[](hashedTest const& lhs, hashedTest const& rhs) {
|
||||
if (lhs.first == rhs.first) {
|
||||
return lhs.second->name < rhs.second->name;
|
||||
}
|
||||
return lhs.first < rhs.first;
|
||||
});
|
||||
|
||||
std::vector<TestCase> sorted;
|
||||
sorted.reserve( indexed_tests.size() );
|
||||
|
||||
for (auto const& hashed : indexed_tests) {
|
||||
sorted.emplace_back(*hashed.second);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
}
|
||||
return sorted;
|
||||
return unsortedTestCases;
|
||||
}
|
||||
|
||||
bool isThrowSafe( TestCase const& testCase, IConfig const& config ) {
|
||||
@ -14591,6 +14665,7 @@ namespace Catch {
|
||||
m_pos = m_arg.size();
|
||||
m_substring.clear();
|
||||
m_patternName.clear();
|
||||
m_realPatternPos = 0;
|
||||
return false;
|
||||
}
|
||||
endMode();
|
||||
@ -14609,6 +14684,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
m_patternName.clear();
|
||||
m_realPatternPos = 0;
|
||||
|
||||
return token;
|
||||
}
|
||||
@ -15079,7 +15155,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 2, 11, 3, "", 0 );
|
||||
static Version version( 2, 12, 0, "", 0 );
|
||||
return version;
|
||||
}
|
||||
|
||||
@ -16142,7 +16218,7 @@ ConsoleReporter::ConsoleReporter(ReporterConfig const& config)
|
||||
else
|
||||
{
|
||||
return{
|
||||
{ "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left },
|
||||
{ "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left },
|
||||
{ "samples mean std dev", 14, ColumnInfo::Right },
|
||||
{ "iterations low mean low std dev", 14, ColumnInfo::Right },
|
||||
{ "estimated high mean high std dev", 14, ColumnInfo::Right }
|
||||
|
Loading…
Reference in New Issue
Block a user