common_test.h revision 00f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abac
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include <dirent.h> 18#include <dlfcn.h> 19#include <sys/mman.h> 20#include <sys/stat.h> 21#include <sys/types.h> 22 23#include "class_linker.h" 24#include "class_loader.h" 25#include "compiler.h" 26#include "dex_file.h" 27#include "file.h" 28#include "gtest/gtest.h" 29#include "heap.h" 30#include "instruction_set.h" 31#include "macros.h" 32#include "oat_file.h" 33#include "object_utils.h" 34#include "os.h" 35#include "runtime.h" 36#include "ScopedLocalRef.h" 37#include "scoped_thread_state_change.h" 38#include "stl_util.h" 39#include "stringprintf.h" 40#include "thread.h" 41#include "unicode/uclean.h" 42#include "unicode/uvernum.h" 43#include "UniquePtr.h" 44#include "well_known_classes.h" 45 46namespace art { 47 48static const byte kBase64Map[256] = { 49 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 50 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 51 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 52 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, 53 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 54 255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 55 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, // NOLINT 56 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, // NOLINT 57 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 58 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, // NOLINT 59 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, // NOLINT 60 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 61 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 64 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 66 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 67 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 68 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 69 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 70 255, 255, 255, 255 71}; 72 73byte* DecodeBase64(const char* src, size_t* dst_size) { 74 std::vector<byte> tmp; 75 uint32_t t = 0, y = 0; 76 int g = 3; 77 for (size_t i = 0; src[i] != '\0'; ++i) { 78 byte c = kBase64Map[src[i] & 0xFF]; 79 if (c == 255) continue; 80 // the final = symbols are read and used to trim the remaining bytes 81 if (c == 254) { 82 c = 0; 83 // prevent g < 0 which would potentially allow an overflow later 84 if (--g < 0) { 85 *dst_size = 0; 86 return NULL; 87 } 88 } else if (g != 3) { 89 // we only allow = to be at the end 90 *dst_size = 0; 91 return NULL; 92 } 93 t = (t << 6) | c; 94 if (++y == 4) { 95 tmp.push_back((t >> 16) & 255); 96 if (g > 1) { 97 tmp.push_back((t >> 8) & 255); 98 } 99 if (g > 2) { 100 tmp.push_back(t & 255); 101 } 102 y = t = 0; 103 } 104 } 105 if (y != 0) { 106 *dst_size = 0; 107 return NULL; 108 } 109 UniquePtr<byte[]> dst(new byte[tmp.size()]); 110 if (dst_size != NULL) { 111 *dst_size = tmp.size(); 112 } else { 113 *dst_size = 0; 114 } 115 std::copy(tmp.begin(), tmp.end(), dst.get()); 116 return dst.release(); 117} 118 119static inline const DexFile* OpenDexFileBase64(const char* base64, 120 const std::string& location) { 121 // decode base64 122 CHECK(base64 != NULL); 123 size_t length; 124 UniquePtr<byte[]> dex_bytes(DecodeBase64(base64, &length)); 125 CHECK(dex_bytes.get() != NULL); 126 127 // write to provided file 128 UniquePtr<File> file(OS::OpenFile(location.c_str(), true)); 129 CHECK(file.get() != NULL); 130 if (!file->WriteFully(dex_bytes.get(), length)) { 131 PLOG(FATAL) << "Failed to write base64 as dex file"; 132 } 133 file.reset(); 134 135 // read dex file 136 const DexFile* dex_file = DexFile::Open(location, location); 137 CHECK(dex_file != NULL); 138 return dex_file; 139} 140 141class ScratchFile { 142 public: 143 ScratchFile() { 144 filename_ = getenv("ANDROID_DATA"); 145 filename_ += "/TmpFile-XXXXXX"; 146 fd_ = mkstemp(&filename_[0]); 147 CHECK_NE(-1, fd_); 148 file_.reset(OS::FileFromFd(GetFilename().c_str(), fd_)); 149 } 150 151 ~ScratchFile() { 152 int unlink_result = unlink(filename_.c_str()); 153 CHECK_EQ(0, unlink_result); 154 int close_result = close(fd_); 155 CHECK_EQ(0, close_result); 156 } 157 158 const std::string& GetFilename() const { 159 return filename_; 160 } 161 162 File* GetFile() const { 163 return file_.get(); 164 } 165 166 int GetFd() const { 167 return fd_; 168 } 169 170 private: 171 std::string filename_; 172 int fd_; 173 UniquePtr<File> file_; 174}; 175 176class CommonTest : public testing::Test { 177 public: 178 static void MakeExecutable(const ByteArray* code_array) { 179 CHECK(code_array != NULL); 180 MakeExecutable(code_array->GetData(), code_array->GetLength()); 181 } 182 183 static void MakeExecutable(const std::vector<uint8_t>& code) { 184 CHECK_NE(code.size(), 0U); 185 MakeExecutable(&code[0], code.size()); 186 } 187 188 // Create an OatMethod based on pointers (for unit tests) 189 OatFile::OatMethod CreateOatMethod(const void* code, 190 const size_t frame_size_in_bytes, 191 const uint32_t core_spill_mask, 192 const uint32_t fp_spill_mask, 193 const uint32_t* mapping_table, 194 const uint16_t* vmap_table, 195 const uint8_t* gc_map, 196 const Method::InvokeStub* invoke_stub) { 197 return OatFile::OatMethod(NULL, 198 reinterpret_cast<uint32_t>(code), 199 frame_size_in_bytes, 200 core_spill_mask, 201 fp_spill_mask, 202 reinterpret_cast<uint32_t>(mapping_table), 203 reinterpret_cast<uint32_t>(vmap_table), 204 reinterpret_cast<uint32_t>(gc_map), 205 reinterpret_cast<uint32_t>(invoke_stub) 206#if defined(ART_USE_LLVM_COMPILER) 207 , 0 208#endif 209 ); 210 } 211 212 void MakeExecutable(Method* method) SHARED_LOCKS_REQUIRED(GlobalSynchronization::mutator_lock_) { 213 CHECK(method != NULL); 214 215 MethodHelper mh(method); 216 const CompiledInvokeStub* compiled_invoke_stub = 217 compiler_->FindInvokeStub(mh.IsStatic(), mh.GetShorty()); 218 CHECK(compiled_invoke_stub != NULL) << PrettyMethod(method); 219 220 const std::vector<uint8_t>& invoke_stub = compiled_invoke_stub->GetCode(); 221 MakeExecutable(invoke_stub); 222 const Method::InvokeStub* method_invoke_stub = 223 reinterpret_cast<const Method::InvokeStub*>( 224 CompiledCode::CodePointer(&invoke_stub[0], 225 compiled_invoke_stub->GetInstructionSet())); 226 227 LOG(INFO) << "MakeExecutable " << PrettyMethod(method) 228 << " invoke_stub=" << reinterpret_cast<void*>(method_invoke_stub); 229 230 if (!method->IsAbstract()) { 231 const DexCache* dex_cache = method->GetDeclaringClass()->GetDexCache(); 232 const DexFile& dex_file = Runtime::Current()->GetClassLinker()->FindDexFile(dex_cache); 233 const CompiledMethod* compiled_method = 234 compiler_->GetCompiledMethod(Compiler::MethodReference(&dex_file, 235 method->GetDexMethodIndex())); 236 CHECK(compiled_method != NULL) << PrettyMethod(method); 237 238 const std::vector<uint8_t>& code = compiled_method->GetCode(); 239 MakeExecutable(code); 240 const void* method_code = CompiledMethod::CodePointer(&code[0], 241 compiled_method->GetInstructionSet()); 242 243 LOG(INFO) << "MakeExecutable " << PrettyMethod(method) << " code=" << method_code; 244 245 OatFile::OatMethod oat_method = CreateOatMethod(method_code, 246 compiled_method->GetFrameSizeInBytes(), 247 compiled_method->GetCoreSpillMask(), 248 compiled_method->GetFpSpillMask(), 249 &compiled_method->GetMappingTable()[0], 250 &compiled_method->GetVmapTable()[0], 251 NULL, 252 method_invoke_stub); 253 oat_method.LinkMethodPointers(method); 254 } else { 255 MakeExecutable(runtime_->GetAbstractMethodErrorStubArray()); 256 const void* method_code = runtime_->GetAbstractMethodErrorStubArray()->GetData(); 257 LOG(INFO) << "MakeExecutable " << PrettyMethod(method) << " code=" << method_code; 258 OatFile::OatMethod oat_method = CreateOatMethod(method_code, 259 kStackAlignment, 260 0, 261 0, 262 NULL, 263 NULL, 264 NULL, 265 method_invoke_stub); 266 oat_method.LinkMethodPointers(method); 267 } 268 } 269 270 static void MakeExecutable(const void* code_start, size_t code_length) { 271 CHECK(code_start != NULL); 272 CHECK_NE(code_length, 0U); 273 uintptr_t data = reinterpret_cast<uintptr_t>(code_start); 274 uintptr_t base = RoundDown(data, kPageSize); 275 uintptr_t limit = RoundUp(data + code_length, kPageSize); 276 uintptr_t len = limit - base; 277 int result = mprotect(reinterpret_cast<void*>(base), len, PROT_READ | PROT_WRITE | PROT_EXEC); 278 CHECK_EQ(result, 0); 279 280 // Flush instruction cache 281 // Only uses __builtin___clear_cache if GCC >= 4.3.3 282#if GCC_VERSION >= 40303 283 __builtin___clear_cache(reinterpret_cast<void*>(base), reinterpret_cast<void*>(base + len)); 284#elif defined(__APPLE__) 285 // Currently, only Mac OS builds use GCC 4.2.*. Those host builds do not 286 // need to generate clear_cache on x86. 287#else 288#error unsupported 289#endif 290 } 291 292 protected: 293 virtual void SetUp() { 294 is_host_ = getenv("ANDROID_BUILD_TOP") != NULL; 295 296 if (is_host_) { 297 // $ANDROID_ROOT is set on the device, but not on the host. 298 // We need to set this so that icu4c can find its locale data. 299 std::string root; 300 root += getenv("ANDROID_BUILD_TOP"); 301#if defined(__linux__) 302 root += "/out/host/linux-x86"; 303#elif defined(__APPLE__) 304 root += "/out/host/darwin-x86"; 305#else 306#error unsupported OS 307#endif 308 setenv("ANDROID_ROOT", root.c_str(), 1); 309 } 310 311 // On target, Cannot use /mnt/sdcard because it is mounted noexec, so use subdir of art-cache 312 android_data_ = (is_host_ ? "/tmp/art-data-XXXXXX" : "/data/art-cache/art-data-XXXXXX"); 313 if (mkdtemp(&android_data_[0]) == NULL) { 314 PLOG(FATAL) << "mkdtemp(\"" << &android_data_[0] << "\") failed"; 315 } 316 setenv("ANDROID_DATA", android_data_.c_str(), 1); 317 art_cache_.append(android_data_.c_str()); 318 art_cache_.append("/art-cache"); 319 int mkdir_result = mkdir(art_cache_.c_str(), 0700); 320 ASSERT_EQ(mkdir_result, 0); 321 322 java_lang_dex_file_ = DexFile::Open(GetLibCoreDexFileName(), GetLibCoreDexFileName()); 323 boot_class_path_.push_back(java_lang_dex_file_); 324 325 std::string min_heap_string(StringPrintf("-Xms%zdm", Heap::kInitialSize / MB)); 326 std::string max_heap_string(StringPrintf("-Xmx%zdm", Heap::kMaximumSize / MB)); 327 328 Runtime::Options options; 329 options.push_back(std::make_pair("compiler", reinterpret_cast<void*>(NULL))); 330 options.push_back(std::make_pair("bootclasspath", &boot_class_path_)); 331 options.push_back(std::make_pair("-Xcheck:jni", reinterpret_cast<void*>(NULL))); 332 options.push_back(std::make_pair(min_heap_string.c_str(), reinterpret_cast<void*>(NULL))); 333 options.push_back(std::make_pair(max_heap_string.c_str(), reinterpret_cast<void*>(NULL))); 334 if(!Runtime::Create(options, false)) { 335 LOG(FATAL) << "Failed to create runtime"; 336 return; 337 } 338 runtime_.reset(Runtime::Current()); 339 // Runtime::Create acquired the mutator_lock_ that is normally given away when we Runtime::Start, 340 // give it away now and then switch to a more managable ScopedObjectAccess. 341 Thread::Current()->TransitionFromRunnableToSuspended(kNative); 342 // Whilst we're in native take the opportunity to initialize well known classes. 343 WellKnownClasses::InitClasses(Thread::Current()->GetJniEnv()); 344 ScopedObjectAccess soa(Thread::Current()); 345 ASSERT_TRUE(runtime_.get() != NULL); 346 class_linker_ = runtime_->GetClassLinker(); 347 348 InstructionSet instruction_set = kNone; 349#if defined(__i386__) 350 instruction_set = kX86; 351#elif defined(__arm__) 352 instruction_set = kThumb2; 353#endif 354 runtime_->SetJniDlsymLookupStub(Compiler::CreateJniDlsymLookupStub(instruction_set)); 355 runtime_->SetAbstractMethodErrorStubArray(Compiler::CreateAbstractMethodErrorStub(instruction_set)); 356 for (int i = 0; i < Runtime::kLastTrampolineMethodType; i++) { 357 Runtime::TrampolineType type = Runtime::TrampolineType(i); 358 if (!runtime_->HasResolutionStubArray(type)) { 359 runtime_->SetResolutionStubArray( 360 Compiler::CreateResolutionStub(instruction_set, type), type); 361 } 362 } 363 if (!runtime_->HasResolutionMethod()) { 364 runtime_->SetResolutionMethod(runtime_->CreateResolutionMethod()); 365 } 366 for (int i = 0; i < Runtime::kLastCalleeSaveType; i++) { 367 Runtime::CalleeSaveType type = Runtime::CalleeSaveType(i); 368 if (!runtime_->HasCalleeSaveMethod(type)) { 369 runtime_->SetCalleeSaveMethod( 370 runtime_->CreateCalleeSaveMethod(instruction_set, type), type); 371 } 372 } 373 class_linker_->FixupDexCaches(runtime_->GetResolutionMethod()); 374 image_classes_.reset(new std::set<std::string>); 375 compiler_.reset(new Compiler(instruction_set, true, 2, false, image_classes_.get(), 376 true, true)); 377 378 runtime_->GetHeap()->VerifyHeap(); // Check for heap corruption before the test 379 } 380 381 virtual void TearDown() { 382 const char* android_data = getenv("ANDROID_DATA"); 383 ASSERT_TRUE(android_data != NULL); 384 DIR* dir = opendir(art_cache_.c_str()); 385 ASSERT_TRUE(dir != NULL); 386 while (true) { 387 dirent entry; 388 dirent* entry_ptr; 389 int readdir_result = readdir_r(dir, &entry, &entry_ptr); 390 ASSERT_EQ(0, readdir_result); 391 if (entry_ptr == NULL) { 392 break; 393 } 394 if ((strcmp(entry_ptr->d_name, ".") == 0) || (strcmp(entry_ptr->d_name, "..") == 0)) { 395 continue; 396 } 397 std::string filename(art_cache_); 398 filename.push_back('/'); 399 filename.append(entry_ptr->d_name); 400 int unlink_result = unlink(filename.c_str()); 401 ASSERT_EQ(0, unlink_result); 402 } 403 closedir(dir); 404 int rmdir_cache_result = rmdir(art_cache_.c_str()); 405 ASSERT_EQ(0, rmdir_cache_result); 406 int rmdir_data_result = rmdir(android_data_.c_str()); 407 ASSERT_EQ(0, rmdir_data_result); 408 409 // icu4c has a fixed 10-element array "gCommonICUDataArray". 410 // If we run > 10 tests, we fill that array and u_setCommonData fails. 411 // There's a function to clear the array, but it's not public... 412 typedef void (*IcuCleanupFn)(); 413 void* sym = dlsym(RTLD_DEFAULT, "u_cleanup_" U_ICU_VERSION_SHORT); 414 CHECK(sym != NULL); 415 IcuCleanupFn icu_cleanup_fn = reinterpret_cast<IcuCleanupFn>(sym); 416 (*icu_cleanup_fn)(); 417 418 compiler_.reset(); 419 image_classes_.reset(); 420 STLDeleteElements(&opened_dex_files_); 421 422 Runtime::Current()->GetHeap()->VerifyHeap(); // Check for heap corruption after the test 423 } 424 425 std::string GetLibCoreDexFileName() { 426 if (is_host_) { 427 const char* host_dir = getenv("ANDROID_HOST_OUT"); 428 CHECK(host_dir != NULL); 429 return StringPrintf("%s/framework/core-hostdex.jar", host_dir); 430 } 431 return StringPrintf("%s/framework/core.jar", GetAndroidRoot()); 432 } 433 434 const DexFile* OpenTestDexFile(const char* name) { 435 CHECK(name != NULL); 436 std::string filename; 437 if (is_host_) { 438 filename += getenv("ANDROID_HOST_OUT"); 439 filename += "/framework/"; 440 } else { 441 filename += "/data/nativetest/art/"; 442 } 443 filename += "art-test-dex-"; 444 filename += name; 445 filename += ".jar"; 446 const DexFile* dex_file = DexFile::Open(filename, filename); 447 CHECK(dex_file != NULL) << "Failed to open " << filename; 448 opened_dex_files_.push_back(dex_file); 449 return dex_file; 450 } 451 452 jobject LoadDex(const char* dex_name) 453 SHARED_LOCKS_REQUIRED(GlobalSynchronization::mutator_lock_) { 454 const DexFile* dex_file = OpenTestDexFile(dex_name); 455 CHECK(dex_file != NULL); 456 class_linker_->RegisterDexFile(*dex_file); 457 std::vector<const DexFile*> class_path; 458 class_path.push_back(dex_file); 459 ScopedObjectAccessUnchecked soa(Thread::Current()); 460 ScopedLocalRef<jobject> class_loader_local(soa.Env(), 461 soa.Env()->AllocObject(WellKnownClasses::dalvik_system_PathClassLoader)); 462 jobject class_loader = soa.Env()->NewGlobalRef(class_loader_local.get()); 463 soa.Self()->SetClassLoaderOverride(soa.Decode<ClassLoader*>(class_loader_local.get())); 464 Runtime::Current()->SetCompileTimeClassPath(class_loader, class_path); 465 return class_loader; 466 } 467 468 void CompileClass(ClassLoader* class_loader, const char* class_name) { 469 std::string class_descriptor(DotToDescriptor(class_name)); 470 Class* klass = class_linker_->FindClass(class_descriptor.c_str(), class_loader); 471 CHECK(klass != NULL) << "Class not found " << class_name; 472 for (size_t i = 0; i < klass->NumDirectMethods(); i++) { 473 CompileMethod(klass->GetDirectMethod(i)); 474 } 475 for (size_t i = 0; i < klass->NumVirtualMethods(); i++) { 476 CompileMethod(klass->GetVirtualMethod(i)); 477 } 478 } 479 480 void CompileMethod(Method* method) SHARED_LOCKS_REQUIRED(GlobalSynchronization::mutator_lock_) { 481 CHECK(method != NULL); 482 compiler_->CompileOne(method); 483 MakeExecutable(method); 484 485 MakeExecutable(runtime_->GetJniDlsymLookupStub()); 486 } 487 488 void CompileDirectMethod(ClassLoader* class_loader, 489 const char* class_name, 490 const char* method_name, 491 const char* signature) 492 SHARED_LOCKS_REQUIRED(GlobalSynchronization::mutator_lock_) { 493 std::string class_descriptor(DotToDescriptor(class_name)); 494 Class* klass = class_linker_->FindClass(class_descriptor.c_str(), class_loader); 495 CHECK(klass != NULL) << "Class not found " << class_name; 496 Method* method = klass->FindDirectMethod(method_name, signature); 497 CHECK(method != NULL) << "Direct method not found: " 498 << class_name << "." << method_name << signature; 499 CompileMethod(method); 500 } 501 502 void CompileVirtualMethod(ClassLoader* class_loader, 503 const char* class_name, 504 const char* method_name, 505 const char* signature) 506 SHARED_LOCKS_REQUIRED(GlobalSynchronization::mutator_lock_) { 507 std::string class_descriptor(DotToDescriptor(class_name)); 508 Class* klass = class_linker_->FindClass(class_descriptor.c_str(), class_loader); 509 CHECK(klass != NULL) << "Class not found " << class_name; 510 Method* method = klass->FindVirtualMethod(method_name, signature); 511 CHECK(method != NULL) << "Virtual method not found: " 512 << class_name << "." << method_name << signature; 513 CompileMethod(method); 514 } 515 516 bool is_host_; 517 std::string android_data_; 518 std::string art_cache_; 519 const DexFile* java_lang_dex_file_; // owned by runtime_ 520 std::vector<const DexFile*> boot_class_path_; 521 UniquePtr<Runtime> runtime_; 522 // Owned by the runtime 523 ClassLinker* class_linker_; 524 UniquePtr<Compiler> compiler_; 525 UniquePtr<std::set<std::string> > image_classes_; 526 527 private: 528 std::vector<const DexFile*> opened_dex_files_; 529}; 530 531// Sets a CheckJni abort hook to catch failures. Note that this will cause CheckJNI to carry on 532// rather than aborting, so be careful! 533class CheckJniAbortCatcher { 534 public: 535 CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) { 536 vm_->check_jni_abort_hook = Hook; 537 vm_->check_jni_abort_hook_data = &actual_; 538 } 539 540 ~CheckJniAbortCatcher() { 541 vm_->check_jni_abort_hook = NULL; 542 vm_->check_jni_abort_hook_data = NULL; 543 EXPECT_TRUE(actual_.empty()) << actual_; 544 } 545 546 void Check(const char* expected_text) { 547 EXPECT_TRUE(actual_.find(expected_text) != std::string::npos) << "\n" 548 << "Expected to find: " << expected_text << "\n" 549 << "In the output : " << actual_; 550 actual_.clear(); 551 } 552 553 private: 554 static void Hook(void* data, const std::string& reason) { 555 // We use += because when we're hooking the aborts like this, multiple problems can be found. 556 *reinterpret_cast<std::string*>(data) += reason; 557 } 558 559 JavaVMExt* vm_; 560 std::string actual_; 561 562 DISALLOW_COPY_AND_ASSIGN(CheckJniAbortCatcher); 563}; 564 565} // namespace art 566 567namespace std { 568 569// TODO: isn't gtest supposed to be able to print STL types for itself? 570template <typename T> 571std::ostream& operator<<(std::ostream& os, const std::vector<T>& rhs) { 572 os << ::art::ToString(rhs); 573 return os; 574} 575 576} // namespace std 577