Add test for thread-attach naming behavior

The interaction between pthread_setname_np and AttachCurrrentThread is
not always obvious. Add a test to codify the interaction. Basically
AttachCurrentThread will always override the current
pthread_setname_np thread name, including if AttachCurrentThread is
called without an explicit name, in which case the pthread-name will
be changed to the name selected by java.

Test: ./test.py --host
Bug: 168655382
Change-Id: If0319b733dc808a4cb892b6199030657c3e69f81
diff --git a/test/2037-thread-name-inherit/Android.bp b/test/2037-thread-name-inherit/Android.bp
new file mode 100644
index 0000000..8cda6b5
--- /dev/null
+++ b/test/2037-thread-name-inherit/Android.bp
@@ -0,0 +1,19 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2037-thread-name-inherit`.
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2037-thread-name-inherit",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+    data: [":art-run-test-2037-thread-name-inherit-expected"],
+}
+
+// Test's expected output.
+genrule {
+    name: "art-run-test-2037-thread-name-inherit-expected",
+    out: ["art-run-test-2037-thread-name-inherit-expected.txt"],
+    srcs: ["expected.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2037-thread-name-inherit/expected.txt b/test/2037-thread-name-inherit/expected.txt
new file mode 100644
index 0000000..6a5618e
--- /dev/null
+++ b/test/2037-thread-name-inherit/expected.txt
@@ -0,0 +1 @@
+JNI_OnLoad called
diff --git a/test/2037-thread-name-inherit/info.txt b/test/2037-thread-name-inherit/info.txt
new file mode 100644
index 0000000..f659e84
--- /dev/null
+++ b/test/2037-thread-name-inherit/info.txt
@@ -0,0 +1 @@
+Test that thread's names are correctly inherited.
diff --git a/test/2037-thread-name-inherit/src/Main.java b/test/2037-thread-name-inherit/src/Main.java
new file mode 100644
index 0000000..18049d7
--- /dev/null
+++ b/test/2037-thread-name-inherit/src/Main.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.function.*;
+
+public class Main {
+  public static final boolean IS_ART = System.getProperty("java.vm.name").equals("Dalvik");
+
+  public static final class Names {
+    public final String native_name;
+    public final String java_name;
+
+    public Names(String ntv, String java) {
+      this.native_name = ntv;
+      this.java_name = java;
+    }
+
+    public boolean equals(Object o) {
+      if (o instanceof Names) {
+        Names on = (Names) o;
+        return on.native_name.equals(native_name) && on.java_name.equals(java_name);
+      } else {
+        return false;
+      }
+    }
+
+    public String toString() {
+      return "Names{native: \"" + native_name + "\", java: \"" + java_name + "\"}";
+    }
+  }
+
+  public static void checkDefaultNames(Names res) {
+    if (IS_ART) {
+      if (!res.native_name.matches("Thread-[0-9]+")) {
+        throw new Error("Bad thread name! " + res);
+      }
+    } else {
+      if (!res.native_name.equals("native-thread")) {
+        throw new Error("Bad thread name! " + res);
+      }
+    }
+    if (!res.java_name.matches("Thread-[0-9]+")) {
+      throw new Error("Bad thread name! " + res);
+    }
+  }
+
+  public static void checkNames(Names res, Names art_exp, Names ri_exp) {
+    if (IS_ART) {
+      if (!res.equals(art_exp)) {
+        throw new Error("Not equal " + res + " != " + art_exp);
+      }
+    } else {
+      if (!res.equals(ri_exp)) {
+        throw new Error("Not equal " + res + " != " + ri_exp);
+      }
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
+    Names[] name = new Names[1];
+    BiConsumer<String, Thread> thdResult =
+        (String native_name, Thread jthread) -> {
+          name[0] = new Names(native_name, jthread.getName());
+        };
+
+    runThreadTest(thdResult);
+    checkDefaultNames(name[0]);
+
+    runThreadTestWithName(thdResult);
+    checkNames(
+        name[0],
+        new Names("java-native-thr", "java-native-thread"),
+        new Names("native-thread", "java-native-thread"));
+
+    runThreadTestSetJava(thdResult);
+    checkNames(
+        name[0],
+        new Names("native-thread-s", "native-thread-set-java"),
+        new Names("native-thread", "native-thread-set-java"));
+  }
+
+  public static native void runThreadTest(BiConsumer<String, Thread> results);
+
+  public static native void runThreadTestWithName(BiConsumer<String, Thread> results);
+
+  public static native void runThreadTestSetJava(BiConsumer<String, Thread> results);
+}
diff --git a/test/2037-thread-name-inherit/thread_name_inherit.cc b/test/2037-thread-name-inherit/thread_name_inherit.cc
new file mode 100644
index 0000000..1cca514
--- /dev/null
+++ b/test/2037-thread-name-inherit/thread_name_inherit.cc
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+
+#include <iostream>
+
+#include "android-base/logging.h"
+
+struct ThreadArgs {
+  JavaVM* jvm;
+  jobject consumer;
+  JavaVMAttachArgs* attach_args;
+  bool set_in_java;
+};
+
+// The main method of the test thread. The ThreadArgs controls what this does.
+void* ThreadMain(void* arg) {
+  ThreadArgs* args = reinterpret_cast<ThreadArgs*>(arg);
+  JNIEnv* env = nullptr;
+  pthread_t self = pthread_self();
+
+  int err = pthread_setname_np(self, "native-thread");
+  CHECK_EQ(err, 0);
+
+  args->jvm->AttachCurrentThread(&env, args->attach_args);
+
+  jclass thread_class = env->FindClass("java/lang/Thread");
+  jclass consumer_class = env->FindClass("java/util/function/BiConsumer");
+  jmethodID current_thread_method =
+      env->GetStaticMethodID(thread_class, "currentThread", "()Ljava/lang/Thread;");
+  jmethodID accept_method =
+      env->GetMethodID(consumer_class, "accept", "(Ljava/lang/Object;Ljava/lang/Object;)V");
+  jobject current_thread = env->CallStaticObjectMethod(thread_class, current_thread_method);
+  if (args->set_in_java) {
+    jmethodID set_name_method = env->GetMethodID(thread_class, "setName", "(Ljava/lang/String;)V");
+    jobject str_name = env->NewStringUTF("native-thread-set-java");
+    env->CallVoidMethod(current_thread, set_name_method, str_name);
+  }
+
+  char name_chars[1024];
+  err = pthread_getname_np(self, name_chars, sizeof(name_chars));
+  CHECK_EQ(err, 0);
+  jobject str_name = env->NewStringUTF(name_chars);
+
+  env->CallVoidMethod(args->consumer, accept_method, str_name, current_thread);
+
+  args->jvm->DetachCurrentThread();
+
+  return nullptr;
+}
+
+extern "C" JNIEXPORT void Java_Main_runThreadTestWithName(JNIEnv* env,
+                                                          jclass /*clazz*/,
+                                                          jobject consumer) {
+  jobject global_consumer = env->NewGlobalRef(consumer);
+  JavaVMAttachArgs args;
+  args.group = nullptr;
+  args.name = "java-native-thread";
+  args.version = JNI_VERSION_1_6;
+  ThreadArgs ta {
+    .jvm = nullptr, .consumer = global_consumer, .attach_args = &args, .set_in_java = false
+  };
+  env->GetJavaVM(&ta.jvm);
+  pthread_t child;
+  pthread_create(&child, nullptr, ThreadMain, &ta);
+  void* ret;
+  pthread_join(child, &ret);
+  env->DeleteGlobalRef(ta.consumer);
+}
+
+extern "C" JNIEXPORT void Java_Main_runThreadTest(JNIEnv* env, jclass /*clazz*/, jobject consumer) {
+  jobject global_consumer = env->NewGlobalRef(consumer);
+  ThreadArgs ta {
+    .jvm = nullptr, .consumer = global_consumer, .attach_args = nullptr, .set_in_java = false
+  };
+  env->GetJavaVM(&ta.jvm);
+  pthread_t child;
+  pthread_create(&child, nullptr, ThreadMain, &ta);
+  void* ret;
+  pthread_join(child, &ret);
+  env->DeleteGlobalRef(ta.consumer);
+}
+
+extern "C" JNIEXPORT void Java_Main_runThreadTestSetJava(JNIEnv* env,
+                                                         jclass /*clazz*/,
+                                                         jobject consumer) {
+  jobject global_consumer = env->NewGlobalRef(consumer);
+  ThreadArgs ta {
+    .jvm = nullptr, .consumer = global_consumer, .attach_args = nullptr, .set_in_java = true
+  };
+  env->GetJavaVM(&ta.jvm);
+  pthread_t child;
+  pthread_create(&child, nullptr, ThreadMain, &ta);
+  void* ret;
+  pthread_join(child, &ret);
+  env->DeleteGlobalRef(ta.consumer);
+}
diff --git a/test/Android.bp b/test/Android.bp
index d31de87..514b54d 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -644,6 +644,7 @@
         "2031-zygote-compiled-frame-deopt/native-wait.cc",
         "2033-shutdown-mechanics/native_shutdown.cc",
         "2036-jni-filechannel/jni_filechannel.cc",
+        "2037-thread-name-inherit/thread_name_inherit.cc",
         "common/runtime_state.cc",
         "common/stack_inspect.cc",
     ],