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