Changed-lines coverage: PR changed C/C++ lines covered by tests: 70.76% (271/383) Uncovered changed code (with context): ================================================================================ src/Common/QueryFuzzerDataTypes.cpp ================================================================================ --- uncovered block 53-81 --- 51 | { 52 | /// Geometry is registered as a Variant over every geo alias, so its variants are the authoritative set. >> 53 | static const std::unordered_set names = [] 54 | { >> 55 | std::unordered_set result{"Geometry"}; >> 56 | const auto geometry = DataTypeFactory::instance().get("Geometry"); >> 57 | if (const auto * variant = typeid_cast(geometry.get())) >> 58 | for (const auto & alternative : variant->getVariants()) >> 59 | result.insert(alternative->getName()); >> 60 | return result; >> 61 | }(); >> 62 | return names; 63 | } 64 | 65 | DataTypePtr QueryFuzzer::fuzzContainerChildren(const DataTypePtr & type) 66 | { >> 67 | if (const auto * type_array = typeid_cast(type.get())) >> 68 | return std::make_shared(fuzzDataType(type_array->getNestedType())); >> 69 | if (const auto * type_tuple = typeid_cast(type.get())) 70 | { >> 71 | DataTypes elements = type_tuple->getElements(); >> 72 | fuzzDataTypes(elements); >> 73 | return std::make_shared(elements); 74 | } >> 75 | if (const auto * type_variant = typeid_cast(type.get())) 76 | { >> 77 | DataTypes variants = type_variant->getVariants(); >> 78 | fuzzDataTypes(variants); >> 79 | return std::make_shared(variants); 80 | } >> 81 | return nullptr; 82 | } 83 | --- uncovered block 105-107 --- 103 | if (!parameters.empty() && fuzz_rand() % 3 == 0) 104 | { >> 105 | for (auto & param : parameters) >> 106 | param = fuzzField(param); >> 107 | return true; 108 | } 109 | return false; --- uncovered block 121-171 --- 119 | { 120 | /// SimpleAggregateFunction: fuzz name (candidate set)/args/params, re-validate via the factory. >> 121 | if (const auto * type_simple_aggr >> 122 | = typeid_cast(type->getCustomName())) 123 | { >> 124 | if (fuzz_rand() % 4 != 0) 125 | { >> 126 | String new_name = type_simple_aggr->getFunctionName(); >> 127 | DataTypes new_arg_types = type_simple_aggr->getArgumentsDataTypes(); >> 128 | Array new_parameters = type_simple_aggr->getParameters(); >> 129 | bool changed = fuzzAggregateName(new_name, new_arg_types.size()); >> 130 | changed |= fuzzDataTypes(new_arg_types); >> 131 | changed |= fuzzAggregateParameters(new_parameters); >> 132 | if (changed) 133 | { >> 134 | if (auto fuzzed = makeAggregateFunctionType( >> 135 | new_name, new_arg_types, new_parameters, /*simple=*/true)) >> 136 | return fuzzed; 137 | } 138 | } >> 139 | return type; 140 | } 141 | 142 | /// Nested(a T1, b T2, ...) is an Array(Tuple(...)) alias: fuzz each element, keep the names. >> 143 | if (const auto * type_nested = typeid_cast(type->getCustomName())) 144 | { >> 145 | if (fuzz_rand() % 4 != 0) 146 | { >> 147 | DataTypes new_elems = type_nested->getElements(); >> 148 | fuzzDataTypes(new_elems); >> 149 | return createNested(new_elems, type_nested->getNames()); 150 | } >> 151 | return type; 152 | } 153 | 154 | /// A geo alias emits its own name regardless of the storage, so a mutated storage cannot round-trip 155 | /// under it. Occasionally emit the fuzzed storage type instead, dropping the alias. >> 156 | if (geoAliasNames().contains(type->getCustomName()->getName()) && fuzz_rand() % 4 == 0) 157 | { >> 158 | try 159 | { >> 160 | if (auto fuzzed = fuzzContainerChildren(type)) >> 161 | return fuzzed; 162 | } >> 163 | catch (...) // NOLINT(bugprone-empty-catch) Ok: a fuzzed storage type may violate a container invariant 164 | { >> 165 | return type; 166 | } 167 | } 168 | 169 | /// Any other custom-named type (Bool, ...) is a leaf alias with no children to fuzz. Go straight to the 170 | /// wrapping/replacement tail: returning the type here would freeze it for every seed. >> 171 | return fuzzTypeWrapping(type); 172 | } 173 | --- uncovered block 182-210 --- 180 | if (type_tuple && fuzz_rand() % 4 != 0) 181 | { >> 182 | DataTypes elements = type_tuple->getElements(); >> 183 | fuzzDataTypes(elements); 184 | /// Occasionally add a new alternative >> 185 | if (elements.size() < 10 && fuzz_rand() % 10 == 0) >> 186 | elements.push_back(getRandomType()); 187 | /// Occasionally drop an alternative (keep at least 1) >> 188 | if (elements.size() > 1 && fuzz_rand() % 10 == 0) >> 189 | elements.erase(elements.begin() + fuzz_rand() % elements.size()); >> 190 | if (type_tuple->hasExplicitNames()) 191 | { >> 192 | auto names = type_tuple->getElementNames(); 193 | /// Pad with synthetic names if a field was added, truncate if one was dropped >> 194 | while (names.size() < elements.size()) >> 195 | names.push_back("f" + std::to_string(names.size())); >> 196 | names.resize(elements.size()); >> 197 | return std::make_shared(elements, names); 198 | } >> 199 | return std::make_shared(elements); 200 | } 201 | 202 | const auto * type_map = typeid_cast(type.get()); 203 | if (type_map && fuzz_rand() % 4 != 0) 204 | { >> 205 | auto key_type = fuzzDataType(type_map->getKeyType()); >> 206 | auto value_type = fuzzDataType(type_map->getValueType()); >> 207 | if (!DataTypeMap::isValidKeyType(key_type)) >> 208 | key_type = type_map->getKeyType(); 209 | >> 210 | return std::make_shared(key_type, value_type); 211 | } 212 | --- uncovered block 218-224 --- 216 | size_t tmp = fuzz_rand() % 3; 217 | if (tmp == 0) >> 218 | return fuzzDataType(type_nullable->getNestedType()); 219 | 220 | if (tmp == 1) 221 | { >> 222 | auto nested_type = fuzzDataType(type_nullable->getNestedType()); >> 223 | if (nested_type->canBeInsideNullable()) >> 224 | return std::make_shared(nested_type); 225 | } 226 | } --- uncovered block 239-239 --- 237 | auto nested_type = fuzzDataType(type_low_cardinality->getDictionaryType()); 238 | if (nested_type->canBeInsideLowCardinality()) >> 239 | return std::make_shared(nested_type); 240 | } 241 | } --- uncovered block 247-271 --- 245 | { 246 | /// Mutate length by ±2 (relative) or pick a fresh random size >> 247 | const size_t n = type_fixed_string->getN(); >> 248 | const size_t new_n = (fuzz_rand() % 4 == 0) >> 249 | ? (fuzz_rand() % MAX_FIXEDSTRING_SIZE_WITHOUT_SUSPICIOUS + 1) >> 250 | : std::clamp( >> 251 | std::max(1, static_cast(n) + static_cast(fuzz_rand() % 5) - 2), >> 252 | 1, >> 253 | MAX_FIXEDSTRING_SIZE_WITHOUT_SUSPICIOUS); >> 254 | return std::make_shared(new_n); 255 | } 256 | 257 | const auto * type_datetime = typeid_cast(type.get()); 258 | if (type_datetime && fuzz_rand() % 4 != 0) >> 259 | return makeRandomDateTime(); /// keep it default or fuzz to another valid explicit timezone 260 | 261 | const auto * type_datetime64 = typeid_cast(type.get()); 262 | if (type_datetime64 && fuzz_rand() % 4 != 0) >> 263 | return makeRandomDateTime64(fuzz_rand() % 10); /// scale in [0, 9], keep/fuzz the explicit timezone 264 | 265 | const auto * type_time64 = typeid_cast(type.get()); 266 | if (type_time64 && fuzz_rand() % 4 != 0) >> 267 | return std::make_shared(fuzz_rand() % 10); /// scale in [0, 9] 268 | 269 | const auto * type_dynamic = typeid_cast(type.get()); 270 | if (type_dynamic && fuzz_rand() % 4 != 0) >> 271 | return std::make_shared(fuzz_rand() % 255); 272 | 273 | const auto * type_variant = typeid_cast(type.get()); --- uncovered block 292-292 --- 290 | std::unordered_map typed_paths = type_object->getTypedPaths(); 291 | for (auto & [path, path_type] : typed_paths) >> 292 | path_type = fuzzDataType(path_type); 293 | try 294 | { --- uncovered block 304-310 --- 302 | catch (...) // NOLINT(bugprone-empty-catch) Ok: a fuzzed typed-path type may violate an Object invariant 303 | { >> 304 | return type; 305 | } 306 | } 307 | 308 | const auto * type_qbit = typeid_cast(type.get()); 309 | if (type_qbit && fuzz_rand() % 4 != 0) >> 310 | return makeRandomQBit(); /// Replacing with a fresh valid QBit is a valid, round-trippable mutation. 311 | 312 | /// NOLINTBEGIN(bugprone-macro-parentheses) --- uncovered block 421-431 --- 419 | 420 | /// A skipped path is a compound identifier, so only identifier-shaped values keep the type parseable. >> 421 | static constexpr const char * path_names[] = {"a", "b", "c", "a.b", "a.b.c", "SKIP", "x_1", "some.path"}; >> 422 | static constexpr size_t n_paths = std::size(path_names); 423 | >> 424 | std::vector paths(paths_to_skip.begin(), paths_to_skip.end()); >> 425 | std::sort(paths.begin(), paths.end()); /// Iteration order of the set is unspecified; keep it seed-stable. >> 426 | paths[fuzz_rand() % paths.size()] = path_names[fuzz_rand() % n_paths]; >> 427 | if (paths.size() < 4 && fuzz_rand() % 3 == 0) >> 428 | paths.push_back(path_names[fuzz_rand() % n_paths]); >> 429 | else if (paths.size() > 1 && fuzz_rand() % 3 == 0) >> 430 | paths.erase(paths.begin() + fuzz_rand() % paths.size()); >> 431 | return std::unordered_set(paths.begin(), paths.end()); 432 | } 433 | --- uncovered block 440-449 --- 438 | 439 | /// The DataTypeObject constructor rejects a regexp RE2 cannot compile, so only compilable ones are used. >> 440 | static constexpr const char * regexps[] >> 441 | = {"^a.*$", ".*", "a|b", "[0-9]+", "^$", "(a)(b)?", "\\d{1,3}", "a{2,}", "[[:alpha:]]+", "x(?:y|z)"}; >> 442 | static constexpr size_t n_regexps = std::size(regexps); 443 | >> 444 | path_regexps_to_skip[fuzz_rand() % path_regexps_to_skip.size()] = regexps[fuzz_rand() % n_regexps]; >> 445 | if (path_regexps_to_skip.size() < 4 && fuzz_rand() % 3 == 0) >> 446 | path_regexps_to_skip.push_back(regexps[fuzz_rand() % n_regexps]); >> 447 | else if (path_regexps_to_skip.size() > 1 && fuzz_rand() % 3 == 0) >> 448 | path_regexps_to_skip.erase(path_regexps_to_skip.begin() + fuzz_rand() % path_regexps_to_skip.size()); >> 449 | return path_regexps_to_skip; 450 | } 451 | --- uncovered block 486-487 --- 484 | if (simple) 485 | { >> 486 | DataTypeCustomSimpleAggregateFunction::checkSupportedFunctions(func); >> 487 | result = createSimpleAggregateFunctionType(func, argument_types, parameters); 488 | } 489 | else --- uncovered block 497-497 --- 495 | /// as a quoted literal, so the emitted name no longer reparses. Decline when it does not. 496 | if (!DataTypeFactory::instance().tryGet(result->getName())) >> 497 | return nullptr; 498 | return result; 499 | } --- uncovered block 517-517 --- 515 | if (fuzz_rand() % 3 == 0) 516 | return std::make_shared(fuzzer_timezones[fuzz_rand() % std::size(fuzzer_timezones)]); >> 517 | return std::make_shared(); 518 | } 519 | --- uncovered block 584-584 --- 582 | /// `DataTypeMap`'s constructor rejects a `Nullable`/`LowCardinality(Nullable)` key. 583 | if (!DataTypeMap::isValidKeyType(key_type)) >> 584 | key_type = std::make_shared(); 585 | return std::make_shared(key_type, getRandomType()); 586 | } WARNING: Failed to get start time for [Print Uncovered Code] - start time and duration won't be set --- Coverage counts --- Lines : baseline 938,404/1,084,896 -> current 938,915/1,085,394 (delta +511 / +498) Functions : baseline 807,015/878,508 -> current 807,075/878,538 (delta +60 / +30) Branches : baseline 307,769/390,970 -> current 308,028/391,268 (delta +259 / +298)