Skip to content

Commit

Permalink
Move the hermes instrumentation tests so they can be open sourced.
Browse files Browse the repository at this point in the history
Reviewed By: willholen

Differential Revision: D21420115

fbshipit-source-id: bf83f283e86e3afc4a70dee9d9bc530e305edd74
  • Loading branch information
mhorowitz authored and facebook-github-bot committed May 19, 2020
1 parent 484b88b commit 9d3ed76
Show file tree
Hide file tree
Showing 13 changed files with 823 additions and 0 deletions.
66 changes: 66 additions & 0 deletions android/test/java/com/facebook/hermes/test/HermesEpilogue.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include "HermesEpilogue.h"

#include <hermes/hermes.h>

#include <MappedFileBuffer.h>

namespace facebook {
namespace jsi {
namespace jni {

static jni::local_ref<jbyteArray> metadataParserImpl(
const uint8_t *data,
size_t size) {
std::pair<const uint8_t *, size_t> epiPair =
hermes::HermesRuntime::getBytecodeEpilogue(data, size);
size_t epilogueSize = epiPair.second;
const uint8_t *epilogue = epiPair.first;
if (epilogueSize == 0) {
// No data after HBC content.
return jni::JArrayByte::newArray(0);
} else {
jni::local_ref<jbyteArray> res = jni::JArrayByte::newArray(epilogueSize);
auto resPtr = res->pin();

// copy epilogue into java land
memcpy(reinterpret_cast<uint8_t *>(resPtr.get()), epilogue, epilogueSize);
return res;
}
}

jni::local_ref<jbyteArray> HermesEpilogue::getHermesBytecodeMetadata(
jni::alias_ref<jclass>,
jni::alias_ref<jbyteArray> jByteArray) {
auto bytesPrimArr = jByteArray->pin();
size_t fileSize = bytesPrimArr.size();
const auto bytes = reinterpret_cast<const uint8_t *>(bytesPrimArr.get());
return metadataParserImpl(bytes, fileSize);
}

jni::local_ref<jbyteArray> HermesEpilogue::getHermesBCFileMetadata(
jni::alias_ref<jclass>,
std::string filename) {
MappedFileBuffer buf{filename};
return metadataParserImpl(buf.data(), buf.size());
}

void HermesEpilogue::registerNatives() {
javaClassLocal()->registerNatives({
makeNativeMethod(
"getHermesBytecodeMetadata",
HermesEpilogue::getHermesBytecodeMetadata),
makeNativeMethod(
"getHermesBCFileMetadata", HermesEpilogue::getHermesBCFileMetadata),
});
}

} // namespace jni
} // namespace jsi
} // namespace facebook
45 changes: 45 additions & 0 deletions android/test/java/com/facebook/hermes/test/HermesEpilogue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#ifndef HERMESEPILOGUE_H_
#define HERMESEPILOGUE_H_

#include <fb/fbjni.h>
#include <jni/Registration.h>
#include <jsi/jsi.h>

namespace facebook {
namespace jsi {
namespace jni {

namespace jni = ::facebook::jni;

class HermesEpilogue : public jni::HybridClass<HermesEpilogue> {
public:
constexpr static auto kJavaDescriptor =
"Lcom/facebook/hermes/test/HermesEpilogue;";
static jni::local_ref<jbyteArray> getHermesBytecodeMetadata(
jni::alias_ref<jclass>,
jni::alias_ref<jbyteArray> bytes);
static jni::local_ref<jbyteArray> getHermesBCFileMetadata(
jni::alias_ref<jclass>,
std::string filename);

static void registerNatives();

private:
friend HybridBase;
HermesEpilogue(std::unique_ptr<jsi::Runtime> rt);

std::unique_ptr<jsi::Runtime> runtime_;
};

} // namespace jni
} // namespace jsi
} // namespace facebook

#endif /* HERMESEPILOGUE_H_ */
45 changes: 45 additions & 0 deletions android/test/java/com/facebook/hermes/test/HermesEpilogue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.hermes.test;

import android.annotation.SuppressLint;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.soloader.SoLoader;

public class HermesEpilogue implements AutoCloseable {
static {
SoLoader.loadLibrary("jsijniepi");
}

@DoNotStrip
@SuppressLint("NotAccessedPrivateField")
private final HybridData mHybridData;

/**
* @param bytecode hermes bytecode, note that no validation is done.
* @return Any byte traling the hermes bytecode data in bytes.
*/
public static native byte[] getHermesBytecodeMetadata(byte[] bytecode);

/** Same as getHermesBytecodeMetadata but starting from a file. */
public static native byte[] getHermesBCFileMetadata(String filename);

private HermesEpilogue(HybridData hybridData) {
mHybridData = hybridData;
}

/**
* Explicitly deallocate the Runtime. After a particular instance of the Hermes Runtime has been
* closed, any other calls to it will result in a {@link NullPointerException}.
*/
@Override
public void close() {
mHybridData.resetNative();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.hermes.test;

import java.nio.charset.Charset;

public class HermesEpilogueTestData {
private static final String SRC = "function f(){}; f()";
private static final byte[] hermesBytecode = JSRuntime.compileJavaScript(SRC);

public static byte[] getBytecode() {
return hermesBytecode;
}

public static byte[] getBytecodeWithEpilogue(String epilogue) {
byte[] epilogueAsBytes = epilogue.getBytes(Charset.forName("UTF-8"));
byte[] hermesBytecodeWithEpilogue = new byte[hermesBytecode.length + epilogueAsBytes.length];
System.arraycopy(hermesBytecode, 0, hermesBytecodeWithEpilogue, 0, hermesBytecode.length);
System.arraycopy(
epilogueAsBytes,
0,
hermesBytecodeWithEpilogue,
hermesBytecode.length,
epilogueAsBytes.length);
return hermesBytecodeWithEpilogue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.hermes.test;

import static org.fest.assertions.api.Assertions.assertThat;

import android.test.InstrumentationTestCase;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.Test;

public class HermesInstrumentationTest extends InstrumentationTestCase {

@Test
public void testEvaluatingJavaScript() {
try (JSRuntime rt = JSRuntime.makeHermesRuntime()) {
rt.evaluateJavaScript("x = 1;");
}
}

@Test
public void testLocaleCompare() {
try (JSRuntime rt = JSRuntime.makeHermesRuntime()) {
rt.evaluateJavaScript("compareResult1 = 'a'.localeCompare('a');");
rt.evaluateJavaScript("compareResult2 = 'a'.localeCompare('b');");
rt.evaluateJavaScript("compareResult3 = 'a'.localeCompare('A');");
rt.evaluateJavaScript("compareResult4 = 'b'.localeCompare('A');");
rt.evaluateJavaScript("compareResult5 = '\u00FC'.localeCompare('\u00FC');");
rt.evaluateJavaScript("compareResult6 = '\u00FC'.localeCompare('u');");

int result1 = rt.getGlobalNumberProperty("compareResult1");
assertThat(result1).isEqualTo(0);
int result2 = rt.getGlobalNumberProperty("compareResult2");
assertThat(result2).isEqualTo(-1);
int result3 = rt.getGlobalNumberProperty("compareResult3");
assertThat(result3).isEqualTo(-1);
int result4 = rt.getGlobalNumberProperty("compareResult4");
assertThat(result4).isEqualTo(1);
int result5 = rt.getGlobalNumberProperty("compareResult5");
assertThat(result5).isEqualTo(0);
int result6 = rt.getGlobalNumberProperty("compareResult6");
assertThat(result6).isEqualTo(1);
}
}

@Test
public void testDateFormat() {
Locale defaultLocale = Locale.getDefault();
Locale.setDefault(new Locale("en-US"));

TimeZone defaultZone = TimeZone.getDefault();
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

try (JSRuntime rt = JSRuntime.makeHermesRuntime()) {
rt.evaluateJavaScript(
new StringBuilder()
.append("var date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));\n")
.append("d1 = date.toLocaleDateString();\n")
.append("d2 = date.toLocaleTimeString();\n")
.append("d3 = date.toLocaleString();\n")
.toString());

String result1 = rt.getGlobalStringProperty("d1");
assertThat(result1).isEqualTo("Dec 20, 2012");
String result2 = rt.getGlobalStringProperty("d2");
assertThat(result2).isEqualTo("3:00:00 AM");
String result3 = rt.getGlobalStringProperty("d3");
assertThat(result3).isEqualTo("Dec 20, 2012 3:00:00 AM");
} finally {
Locale.setDefault(defaultLocale);
TimeZone.setDefault(defaultZone);
}
}

@Test
public void testCaseConversion() {
Locale defaultLocale = Locale.getDefault();
Locale.setDefault(new Locale("en-US"));

try (JSRuntime rt = JSRuntime.makeHermesRuntime()) {
rt.evaluateJavaScript(
new StringBuilder()
.append("var s1 = 'abc'.toUpperCase();")
.append("var s2 = '\u00E0'.toUpperCase();")
.toString());
String result1 = rt.getGlobalStringProperty("s1");
assertThat(result1).isEqualTo("ABC");
String result2 = rt.getGlobalStringProperty("s2");
assertThat(result2).isEqualTo("\u00C0");
}
}

@Test
public void testNormalize() {
Locale defaultLocale = Locale.getDefault();
Locale.setDefault(new Locale("en-US"));

try (JSRuntime rt = JSRuntime.makeHermesRuntime()) {
rt.evaluateJavaScript(
new StringBuilder()
.append("var s1 = '\u1E9B\u0323'.normalize('NFC');")
.append("var s2 = '\u1E9B\u0323'.normalize('NFD');")
.append("var s3 = '\u1E9B\u0323'.normalize('NFKC');")
.append("var s4 = '\u1E9B\u0323'.normalize('NFKD');")
.toString());
String result1 = rt.getGlobalStringProperty("s1");
String result2 = rt.getGlobalStringProperty("s2");
String result3 = rt.getGlobalStringProperty("s3");
String result4 = rt.getGlobalStringProperty("s4");
assertThat(result1).isEqualTo("\u1E9B\u0323");
assertThat(result2).isEqualTo("\u017F\u0323\u0307");
assertThat(result3).isEqualTo("\u1E69");
assertThat(result4).isEqualTo("\u0073\u0323\u0307");
}
}

// Gets the recorded GC stats from the runtime, finds the reported heap size,
// parses is a long, and returns that.
private long getHeapSize(JSRuntime rt) {
String pattern = "\"Heap size\": ([0-9]+),";
Pattern r = Pattern.compile(pattern, Pattern.MULTILINE);
Matcher m = r.matcher(rt.getRecordedGCStats());
assertThat(m.find()).isTrue();
return Long.parseLong(m.group(1));
}

@Test
public void testCreateRuntimeWithHeapSpec() {
long M = 1024 * 1024;
// The current default initial heap size is 32M. Verify that.
// In doing such verifications, some heaps, especially NCGen, may round
// the requested size up to meet various constraints -- but never by as much
// as 8MB (the size of an NCGen segment).
try (JSRuntime rt = JSRuntime.makeHermesRuntime(/* shouldRecordGCStats */ true)) {
assertThat(getHeapSize(rt)).isGreaterThanOrEqualTo(32 * M);
assertThat(getHeapSize(rt)).isLessThan(32 * M + 8 * M);
}

// Now show that we can make runtime with a smaller heap with the alternate
// factory function:
try (JSRuntime rt =
JSRuntime.makeHermesRuntimeWithHeapSpec(8 * M, 8 * M, /* shouldRecordGCStats */ true)) {
assertThat(getHeapSize(rt)).isGreaterThanOrEqualTo(8 * M);
assertThat(getHeapSize(rt)).isLessThan(8 * M + 8 * M);
}
}

@Test
public void testGetHermesEpilogue() {
final String EXPECTED_EPILOGUE = "{\"foo\" : \"bar\"}\n";
byte[] s = HermesEpilogue.getHermesBytecodeMetadata(HermesEpilogueTestData.getBytecode());
assertThat(s.length).isZero();

byte[] epilogue =
HermesEpilogue.getHermesBytecodeMetadata(
HermesEpilogueTestData.getBytecodeWithEpilogue(EXPECTED_EPILOGUE));
assertEquals(EXPECTED_EPILOGUE, new String(epilogue));
}
}
Loading

0 comments on commit 9d3ed76

Please sign in to comment.