|
| 1 | +// Copyright (c) Microsoft Corporation. |
| 2 | +// Licensed under the MIT license. |
| 3 | + |
| 4 | +#ifdef _WIN32 |
| 5 | +#include <Windows.h> |
| 6 | +#endif |
| 7 | +#include "result_metadata.hpp" |
| 8 | + |
| 9 | +#include <cassert> |
| 10 | +#include <cstdio> |
| 11 | +#include <cstring> |
| 12 | +#include <new> |
| 13 | +#include <stdexcept> |
| 14 | +#include <thread> |
| 15 | + |
| 16 | +#ifdef NDEBUG |
| 17 | +#error Native metadata tests require assertions, including Release builds. |
| 18 | +#endif |
| 19 | + |
| 20 | +struct TestHandle { |
| 21 | + ResultMetadataCache resultMetadata; |
| 22 | + std::mutex* childMutex = nullptr; |
| 23 | + |
| 24 | + ~TestHandle() { |
| 25 | + if (childMutex) { |
| 26 | + bool acquired = false; |
| 27 | + std::thread observer([&] { |
| 28 | + acquired = childMutex->try_lock(); |
| 29 | + if (acquired) { |
| 30 | + childMutex->unlock(); |
| 31 | + } |
| 32 | + }); |
| 33 | + observer.join(); |
| 34 | + assert(acquired); |
| 35 | + } |
| 36 | + } |
| 37 | +}; |
| 38 | + |
| 39 | +std::weak_ptr<TestHandle> observedHandle; |
| 40 | +bool failAllocation = false; |
| 41 | +long ownersAtFailure = -1; |
| 42 | + |
| 43 | +static std::shared_ptr<const ResultMetadata> MakeMetadata() { |
| 44 | + auto metadata = std::make_shared<ResultMetadata>(); |
| 45 | + metadata->columns.push_back({u"owned", SQL_INTEGER, 10, 0, 1}); |
| 46 | + return metadata; |
| 47 | +} |
| 48 | + |
| 49 | +static void Populate(ResultMetadataCache& cache) { |
| 50 | + const auto snapshot = cache.snapshot(); |
| 51 | + cache.publish(snapshot.generation, MakeMetadata()); |
| 52 | +} |
| 53 | + |
| 54 | +static void TestSnapshots() { |
| 55 | + ResultMetadataCache cache; |
| 56 | + const auto initial = cache.snapshot(); |
| 57 | + assert(!initial.metadata); |
| 58 | + auto metadata = MakeMetadata(); |
| 59 | + std::weak_ptr<const ResultMetadata> weak = metadata; |
| 60 | + cache.publish(initial.generation, metadata); |
| 61 | + auto held = cache.snapshot(); |
| 62 | + assert(held.metadata == metadata); |
| 63 | + cache.clear(); |
| 64 | + assert(!cache.snapshot().metadata); |
| 65 | + assert(cache.snapshot().generation != initial.generation); |
| 66 | + |
| 67 | + Populate(cache); |
| 68 | + const auto replacement = cache.snapshot(); |
| 69 | + cache.publish(initial.generation, metadata); |
| 70 | + assert(cache.snapshot().metadata == replacement.metadata); |
| 71 | + assert(held.metadata->columns.at(0).name == u"owned"); |
| 72 | + metadata.reset(); |
| 73 | + assert(!weak.expired()); |
| 74 | + held.metadata.reset(); |
| 75 | + assert(weak.expired()); |
| 76 | +} |
| 77 | + |
| 78 | +static void TestFailures() { |
| 79 | + ResultMetadataCache cache; |
| 80 | + const SQLRETURN results[] = {SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NO_DATA, |
| 81 | + SQL_ERROR, SQL_INVALID_HANDLE}; |
| 82 | + for (SQLRETURN result : results) { |
| 83 | + Populate(cache); |
| 84 | + const auto before = cache.snapshot(); |
| 85 | + { |
| 86 | + ResultMetadataFailureGuard guard(cache, result); |
| 87 | + } |
| 88 | + const auto after = cache.snapshot(); |
| 89 | + if (SQL_SUCCEEDED(result) || result == SQL_NO_DATA) { |
| 90 | + assert(after.metadata == before.metadata); |
| 91 | + assert(after.generation == before.generation); |
| 92 | + } else { |
| 93 | + assert(!after.metadata); |
| 94 | + assert(after.generation != before.generation); |
| 95 | + } |
| 96 | + } |
| 97 | + Populate(cache); |
| 98 | + SQLRETURN result = SQL_SUCCESS; |
| 99 | + try { |
| 100 | + ResultMetadataFailureGuard guard(cache, result); |
| 101 | + throw std::runtime_error("conversion failure"); |
| 102 | + } catch (const std::runtime_error&) { |
| 103 | + assert(!cache.snapshot().metadata); |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +static void TestConcurrentInvalidation() { |
| 108 | + ResultMetadataCache cache; |
| 109 | + const auto metadata = MakeMetadata(); |
| 110 | + std::thread invalidator([&] { |
| 111 | + for (int i = 0; i < 1000; ++i) { |
| 112 | + cache.clear(); |
| 113 | + } |
| 114 | + }); |
| 115 | + for (int i = 0; i < 1000; ++i) { |
| 116 | + const auto snapshot = cache.snapshot(); |
| 117 | + cache.publish(snapshot.generation, metadata); |
| 118 | + if (snapshot.metadata) { |
| 119 | + assert(snapshot.metadata->columns.at(0).name == u"owned"); |
| 120 | + } |
| 121 | + } |
| 122 | + invalidator.join(); |
| 123 | + cache.clear(); |
| 124 | + assert(!cache.snapshot().metadata); |
| 125 | +} |
| 126 | + |
| 127 | +static void TestChildren() { |
| 128 | + std::mutex childMutex; |
| 129 | + auto first = std::make_shared<TestHandle>(); |
| 130 | + auto second = std::make_shared<TestHandle>(); |
| 131 | + auto unrelated = std::make_shared<TestHandle>(); |
| 132 | + std::vector<std::weak_ptr<TestHandle>> children{first, {}, second}; |
| 133 | + Populate(first->resultMetadata); |
| 134 | + Populate(second->resultMetadata); |
| 135 | + Populate(unrelated->resultMetadata); |
| 136 | + const auto held = first->resultMetadata.snapshot(); |
| 137 | + ClearChildResultMetadata(childMutex, children); |
| 138 | + assert(!first->resultMetadata.snapshot().metadata); |
| 139 | + assert(!second->resultMetadata.snapshot().metadata); |
| 140 | + assert(unrelated->resultMetadata.snapshot().metadata); |
| 141 | + assert(held.metadata->columns.at(0).name == u"owned"); |
| 142 | + ClearChildResultMetadata(childMutex, children); |
| 143 | +} |
| 144 | + |
| 145 | +static void TestAllocationFailure() { |
| 146 | + std::mutex childMutex; |
| 147 | + auto owner = std::make_shared<TestHandle>(); |
| 148 | + observedHandle = owner; |
| 149 | + std::vector<std::weak_ptr<TestHandle>> children{owner}; |
| 150 | + Populate(owner->resultMetadata); |
| 151 | + const auto before = owner->resultMetadata.snapshot(); |
| 152 | + failAllocation = true; |
| 153 | + try { |
| 154 | + ClearChildResultMetadata(childMutex, children); |
| 155 | + assert(false); |
| 156 | + } catch (const std::bad_alloc&) { |
| 157 | + assert(ownersAtFailure == 1); |
| 158 | + assert(owner->resultMetadata.snapshot().metadata == before.metadata); |
| 159 | + assert(childMutex.try_lock()); |
| 160 | + childMutex.unlock(); |
| 161 | + } |
| 162 | + assert(!failAllocation); |
| 163 | + ClearChildResultMetadata(childMutex, children); |
| 164 | + assert(!owner->resultMetadata.snapshot().metadata); |
| 165 | +} |
| 166 | + |
| 167 | +static void TestLastOwner() { |
| 168 | + std::mutex childMutex; |
| 169 | + auto owner = std::make_shared<TestHandle>(); |
| 170 | + owner->childMutex = &childMutex; |
| 171 | + const std::weak_ptr<TestHandle> weak = owner; |
| 172 | + std::vector<std::weak_ptr<TestHandle>> children{owner}; |
| 173 | + // Drop the external owner during invalidation, leaving only the helper's snapshot. |
| 174 | + auto metadata = std::shared_ptr<ResultMetadata>(new ResultMetadata, [&](auto* value) { |
| 175 | + owner.reset(); |
| 176 | + delete value; |
| 177 | + }); |
| 178 | + const auto generation = owner->resultMetadata.snapshot().generation; |
| 179 | + owner->resultMetadata.publish(generation, std::move(metadata)); |
| 180 | + ClearChildResultMetadata(childMutex, children); |
| 181 | + assert(!owner && weak.expired()); |
| 182 | +} |
| 183 | + |
| 184 | +int main(int argc, char** argv) { |
| 185 | + if (argc != 2) { |
| 186 | + std::fputs("Expected one native metadata test case\n", stderr); |
| 187 | + return 2; |
| 188 | + } |
| 189 | + const char* name = argv[1]; |
| 190 | + if (std::strcmp(name, "snapshots") == 0) { |
| 191 | + TestSnapshots(); |
| 192 | + } else if (std::strcmp(name, "failures") == 0) { |
| 193 | + TestFailures(); |
| 194 | + } else if (std::strcmp(name, "concurrent") == 0) { |
| 195 | + TestConcurrentInvalidation(); |
| 196 | + } else if (std::strcmp(name, "children") == 0) { |
| 197 | + TestChildren(); |
| 198 | + } else if (std::strcmp(name, "allocation") == 0) { |
| 199 | + TestAllocationFailure(); |
| 200 | + } else if (std::strcmp(name, "last_owner") == 0) { |
| 201 | + TestLastOwner(); |
| 202 | + } else { |
| 203 | + std::fprintf(stderr, "Unknown native metadata test case: %s\n", name); |
| 204 | + return 2; |
| 205 | + } |
| 206 | + std::printf("%s passed\n", name); |
| 207 | + return 0; |
| 208 | +} |
0 commit comments