Refactor JS test suite to be more in line with other Java projects.

Therefore, we move the test suite out of the `src` directory.
This commit is contained in:
Cornelius Riemenschneider
2023-11-03 12:03:32 +01:00
parent 6c7ea86a12
commit a773532d07
15 changed files with 11 additions and 4 deletions

View File

@@ -1,87 +0,0 @@
package com.semmle.js.extractor.test;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.semmle.jcorn.ESNextParser;
import com.semmle.jcorn.Options;
import com.semmle.jcorn.SyntaxError;
import com.semmle.js.ast.AST2JSON;
import com.semmle.js.ast.Program;
import com.semmle.util.io.WholeIO;
import java.io.File;
import java.util.Iterator;
import java.util.Map.Entry;
import org.junit.Assert;
/** Base class for tests that use Acorn-like AST templates to specify expected test output. */
public abstract class ASTMatchingTests {
/**
* Assert that the given actual sub-AST matches the expected AST template, where {@code path} is
* the path from the root to the sub-AST.
*/
protected void assertMatch(String path, JsonElement expected, JsonElement actual) {
if (expected == null) Assert.assertNull(path + ": null != " + actual, actual);
if (expected instanceof JsonPrimitive || expected instanceof JsonNull)
Assert.assertEquals(path, expected.toString(), actual.toString());
if (expected instanceof JsonArray) {
Assert.assertTrue(path + ": " + expected + " != " + actual, actual instanceof JsonArray);
Iterator<JsonElement> expectedElements = ((JsonArray) expected).iterator();
Iterator<JsonElement> actualElements = ((JsonArray) actual).iterator();
int elt = 0;
while (expectedElements.hasNext()) {
String newPath = path + "[" + elt++ + "]";
Assert.assertTrue(newPath + ": missing", actualElements.hasNext());
assertMatch(newPath, expectedElements.next(), actualElements.next());
}
}
if (expected instanceof JsonObject) {
Assert.assertTrue(path + ": " + expected + " != " + actual, actual instanceof JsonObject);
JsonObject actualObject = (JsonObject) actual;
for (Entry<String, JsonElement> expectedProp : ((JsonObject) expected).entrySet()) {
String key = expectedProp.getKey();
JsonElement value = expectedProp.getValue();
String newPath = path + "." + key;
Assert.assertTrue(newPath + ": missing", actualObject.has(key));
assertMatch(newPath, value, actualObject.get(key));
}
}
}
private static final File BABYLON_BASE = new File("parser-tests/babylon").getAbsoluteFile();
protected void babylonTest(String dir) {
babylonTest(dir, new Options().esnext(true));
}
protected void babylonTest(String dir, Options options) {
File actual = new File(new File(BABYLON_BASE, dir), "actual.js");
String actualSrc = new WholeIO().strictread(actual);
JsonElement actualJSON;
try {
Program actualAST = new ESNextParser(options, actualSrc, 0).parse();
actualJSON = AST2JSON.convert(actualAST);
} catch (SyntaxError e) {
actualJSON = new JsonPrimitive(e.getMessage());
}
File expected = new File(actual.getParentFile(), "expected.ast");
JsonElement expectedJSON;
if (expected.exists()) {
String expectedSrc = new WholeIO().strictread(expected);
expectedJSON = new JsonParser().parse(expectedSrc).getAsJsonObject();
} else {
File expectedErrFile = new File(actual.getParentFile(), "expected.error");
String expectedErrMsg = new WholeIO().strictread(expectedErrFile).trim();
expectedJSON = new JsonPrimitive(expectedErrMsg);
}
assertMatch("<root>", expectedJSON, actualJSON);
}
}

View File

@@ -1,21 +0,0 @@
package com.semmle.js.extractor.test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
JSXTests.class,
NodeJSDetectorTests.class,
TrapTests.class,
ObjectRestSpreadTests.class,
ClassPropertiesTests.class,
FunctionSentTests.class,
DecoratorTests.class,
ExportExtensionsTests.class,
AutoBuildTests.class,
RobustnessTests.class,
NumericSeparatorTests.class
})
public class AllTests {}

View File

@@ -1,631 +0,0 @@
package com.semmle.js.extractor.test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributeView;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import com.semmle.js.extractor.AutoBuild;
import com.semmle.js.extractor.DependencyInstallationResult;
import com.semmle.js.extractor.ExtractorState;
import com.semmle.js.extractor.FileExtractor;
import com.semmle.js.extractor.FileExtractor.FileType;
import com.semmle.js.extractor.VirtualSourceRoot;
import com.semmle.util.data.StringUtil;
import com.semmle.util.exception.UserError;
import com.semmle.util.files.FileUtil;
import com.semmle.util.files.FileUtil8;
import com.semmle.util.process.Env;
public class AutoBuildTests {
private Path SEMMLE_DIST, LGTM_SRC;
private Set<String> expected;
private Map<String, String> envVars;
/**
* Set up fake distribution and source directory and environment variables pointing to them, and
* add in a few fake externs.
*/
@Before
public void setup() throws IOException {
expected = new LinkedHashSet<>();
envVars = new LinkedHashMap<>();
// set up an empty distribution directory with an empty sub-directory for externs
SEMMLE_DIST = Files.createTempDirectory("autobuild-dist").toRealPath();
Path externs =
Files.createDirectories(Paths.get(SEMMLE_DIST.toString(), "tools", "data", "externs"));
// set up environment variables (the value of TRAP_FOLDER and SOURCE_ARCHIVE doesn't
// really matter, since we won't do any actual extraction)
envVars.put(Env.Var.SEMMLE_DIST.toString(), SEMMLE_DIST.toString());
envVars.put(Env.Var.TRAP_FOLDER.toString(), SEMMLE_DIST.toString());
envVars.put(Env.Var.SOURCE_ARCHIVE.toString(), SEMMLE_DIST.toString());
// set up an empty source directory
LGTM_SRC = Files.createTempDirectory("autobuild-src").toRealPath();
envVars.put("LGTM_SRC", LGTM_SRC.toString());
// add a few fake externs
addFile(true, externs, "a.js");
addFile(false, externs, "a.html");
addFile(true, externs, "sub", "b.js");
addFile(false, externs, "sub", "b.json");
}
/** Clean up distribution and source directory, and reset environment. */
@After
public void teardown() throws IOException {
FileUtil8.recursiveDelete(SEMMLE_DIST);
FileUtil8.recursiveDelete(LGTM_SRC);
}
/**
* Add a file under {@code root} that we either do or don't expect to be extracted, depending on
* the value of {@code extracted}. If the file is expected to be extracted, its path is added to
* {@link #expected}. If non-null, parameter {@code fileType} indicates the file type with which
* we expect the file to be extracted.
*/
private Path addFile(boolean extracted, FileType fileType, Path root, String... components)
throws IOException {
Path f = addFile(root, components);
if (extracted) {
expected.add(f + (fileType == null ? "" : ":" + fileType.toString()));
}
return f;
}
/** Add a file with default file type. */
private Path addFile(boolean extracted, Path root, String... components) throws IOException {
return addFile(extracted, null, root, components);
}
/** Create a file at the specified path under {@code root} and return it. */
private Path addFile(Path root, String... components) throws IOException {
Path p = Paths.get(root.toString(), components);
Files.createDirectories(p.getParent());
return Files.createFile(p);
}
/** Run autobuild and compare the set of actually extracted files against {@link #expected}. */
private void runTest() throws IOException {
Env.systemEnv().pushEnvironmentContext(envVars);
try {
Set<String> actual = new LinkedHashSet<>();
new AutoBuild() {
@Override
protected CompletableFuture<?> extract(FileExtractor extractor, Path file, boolean concurrent) {
String extracted = file.toString();
if (extractor.getConfig().hasFileType())
extracted += ":" + extractor.getFileType(file.toFile());
actual.add(extracted);
return CompletableFuture.completedFuture(null);
}
@Override
protected boolean hasSeenCode() {
return true;
}
@Override
public void verifyTypeScriptInstallation(ExtractorState state) {}
@Override
public void extractTypeScriptFiles(
java.util.List<Path> files,
java.util.Set<Path> extractedFiles,
FileExtractors extractors) {
for (Path f : files) {
actual.add(f.toString());
}
}
@Override
protected DependencyInstallationResult preparePackagesAndDependencies(Set<Path> filesToExtract) {
// currently disabled in tests
return DependencyInstallationResult.empty;
}
@Override
protected VirtualSourceRoot makeVirtualSourceRoot() {
return VirtualSourceRoot.none; // not used in these tests
}
@Override
protected void extractXml() throws IOException {
Files.walkFileTree(
LGTM_SRC,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
String ext = FileUtil.extension(file);
if (!ext.isEmpty() && getXmlExtensions().contains(ext.substring(1)))
actual.add(file.toString());
return FileVisitResult.CONTINUE;
}
});
}
}.run();
String expectedString = StringUtil.glue("\n", expected.stream().sorted().toArray());
String actualString = StringUtil.glue("\n", actual.stream().sorted().toArray());
Assert.assertEquals(expectedString, actualString);
} finally {
Env.systemEnv().popEnvironmentContext();
}
}
@Test
public void basicTest() throws IOException {
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "tst.ts");
addFile(true, LGTM_SRC, "tst.html");
addFile(false, LGTM_SRC, "tst.json");
addFile(true, LGTM_SRC, "package.json");
addFile(true, LGTM_SRC, ".eslintrc.yml");
addFile(true, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void typescript() throws IOException {
envVars.put("LGTM_INDEX_TYPESCRIPT", "basic");
addFile(true, LGTM_SRC, "tst.ts");
addFile(true, LGTM_SRC, "tst.tsx");
runTest();
}
@Test(expected = UserError.class)
public void typescriptWrongConfig() throws IOException {
envVars.put("LGTM_INDEX_TYPESCRIPT", "true");
addFile(true, LGTM_SRC, "tst.ts");
addFile(true, LGTM_SRC, "tst.tsx");
runTest();
}
@Test
public void includeFile() throws IOException {
envVars.put("LGTM_INDEX_INCLUDE", "tst.js");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFile() throws IOException {
envVars.put("LGTM_INDEX_EXCLUDE", "vendor/leftpad/index.js");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByPattern() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/vendor");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByPattern2() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "exclude:*/**/vendor");
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "vendor", "dep", "index.js");
addFile(false, LGTM_SRC, "vendor", "dep", "vendor", "depdep", "index.js");
runTest();
}
@Test
public void excludeFolderByPattern3() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/vendor\n");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByPatterns() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "exclude:foo\nexclude:**/vendor");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByName() throws IOException {
envVars.put("LGTM_INDEX_EXCLUDE", "vendor");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByName2() throws IOException {
envVars.put("LGTM_INDEX_EXCLUDE", "vendor\n");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByName3() throws IOException {
envVars.put("LGTM_INDEX_EXCLUDE", "./vendor\n");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeByExtension() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/*.js");
addFile(false, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "tst.html");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
addFile(true, LGTM_SRC, "vendor", "leftpad", "index.html");
runTest();
}
@Test
public void includeByExtension() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "include:**/*.json");
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "tst.json");
addFile(true, LGTM_SRC, "vendor", "leftpad", "tst.json");
runTest();
}
@Test
public void includeByExtensionInRootOnly() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "include:*.json");
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "tst.json");
addFile(false, LGTM_SRC, "vendor", "leftpad", "tst.json");
runTest();
}
@Test
public void includeAndExclude() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "include:**/*.json\n" + "exclude:**/vendor");
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "tst.json");
addFile(false, LGTM_SRC, "vendor", "leftpad", "tst.json");
runTest();
}
@Test
public void excludeByClassification() throws IOException {
Path repositoryFolders = Files.createFile(SEMMLE_DIST.resolve("repositoryFolders.csv"));
List<String> csvLines = new ArrayList<>();
csvLines.add("classification,path");
csvLines.add("thirdparty," + LGTM_SRC.resolve("vendor"));
csvLines.add("external," + LGTM_SRC.resolve("foo").resolve("bar").toUri());
csvLines.add("metadata," + LGTM_SRC.resolve(".git"));
Files.write(repositoryFolders, csvLines, StandardCharsets.UTF_8);
envVars.put("LGTM_REPOSITORY_FOLDERS_CSV", repositoryFolders.toString());
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "foo", "bar", "tst.js");
addFile(false, LGTM_SRC, ".git", "tst.js");
addFile(true, LGTM_SRC, "vendor", "leftpad", "tst.js");
runTest();
}
@Test
public void excludeIncludeNested() throws IOException {
envVars.put("LGTM_INDEX_INCLUDE", ".\ntest/util");
envVars.put("LGTM_INDEX_EXCLUDE", "test\ntest/util/test");
addFile(true, LGTM_SRC, "index.js");
addFile(false, LGTM_SRC, "test", "tst.js");
addFile(false, LGTM_SRC, "test", "subtest", "tst.js");
addFile(true, LGTM_SRC, "test", "util", "util.js");
addFile(false, LGTM_SRC, "test", "util", "test", "utiltst.js");
runTest();
}
@Test
public void includeImplicitlyExcluded() throws IOException {
Path repositoryFolders = Files.createFile(SEMMLE_DIST.resolve("repositoryFolders.csv"));
List<String> csvLines = new ArrayList<>();
csvLines.add("classification,path");
csvLines.add("thirdparty," + LGTM_SRC.resolve("vendor"));
csvLines.add("external," + LGTM_SRC.resolve("foo").resolve("bar"));
csvLines.add("metadata," + LGTM_SRC.resolve(".git"));
Files.write(repositoryFolders, csvLines, StandardCharsets.UTF_8);
envVars.put("LGTM_REPOSITORY_FOLDERS_CSV", repositoryFolders.toString());
envVars.put("LGTM_INDEX_INCLUDE", ".\nfoo/bar");
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "foo", "bar", "tst.js");
addFile(false, LGTM_SRC, ".git", "tst.js");
addFile(true, LGTM_SRC, "vendor", "leftpad", "tst.js");
runTest();
}
/**
* Create a symbolic link from {@code $LGTM_SRC/link} to {@code target} and invoke {@code
* runTest}. Skip running the test if the symbolic link cannot be created.
*/
private void createSymlinkAndRunTest(String link, Path target) throws IOException {
createSymlinkAndRunTest(Paths.get(LGTM_SRC.toString(), link), target);
}
/**
* Create a symbolic link from {@code link} to {@code target} and invoke {@code runTest}. Skip
* running the test if the symbolic link cannot be created.
*/
private void createSymlinkAndRunTest(Path linkPath, Path target) throws IOException {
try {
Files.createSymbolicLink(linkPath, target);
} catch (UnsupportedOperationException | SecurityException | IOException e) {
Assume.assumeNoException("Cannot create symlinks.", e);
}
runTest();
}
@Test
public void symlinkFile() throws IOException {
Path tst_js = addFile(true, LGTM_SRC, "tst.js");
createSymlinkAndRunTest("tst_link.js", tst_js);
}
@Test
public void symlinkDir() throws IOException {
Path testFolder = Files.createTempDirectory("autobuild-test").toAbsolutePath();
try {
addFile(false, testFolder, "tst.js");
createSymlinkAndRunTest("test", testFolder);
} finally {
FileUtil8.recursiveDelete(testFolder);
}
}
@Test
public void deadSymlinkFile() throws IOException {
Path dead = Paths.get("i", "do", "not", "exist", "tst.js");
Assert.assertFalse(Files.exists(dead));
createSymlinkAndRunTest("tst_link.js", dead);
}
@Test
public void deadSymlinkDir() throws IOException {
Path dead = Paths.get("i", "do", "not", "exist");
Assert.assertFalse(Files.exists(dead));
createSymlinkAndRunTest("test", dead);
}
@Test
public void excludeByClassificationSymlink() throws IOException {
// check for robustness in case LGTM_SRC is canonicalised but repositoryFolders.csv is not
Path testFolder = Files.createTempDirectory("autobuild-test").toAbsolutePath();
Files.createDirectories(testFolder);
Path repositoryFolders = Files.createFile(SEMMLE_DIST.resolve("repositoryFolders.csv"));
List<String> csvLines = new ArrayList<>();
csvLines.add("classification,path");
csvLines.add("external," + testFolder.resolve("src").resolve("foo"));
Files.write(repositoryFolders, csvLines, StandardCharsets.UTF_8);
envVars.put("LGTM_REPOSITORY_FOLDERS_CSV", repositoryFolders.toString());
addFile(false, LGTM_SRC, "foo", "tst.js");
createSymlinkAndRunTest(testFolder.resolve("src"), LGTM_SRC);
FileUtil8.recursiveDelete(testFolder);
}
@Test
public void excludeByClassificationBadPath() throws IOException {
// check for robustness in case there are unresolvable paths in repositoryFolders.csv
Path testFolder = Files.createTempDirectory("autobuild-test").toAbsolutePath();
Files.createDirectories(testFolder);
Path repositoryFolders = Files.createFile(SEMMLE_DIST.resolve("repositoryFolders.csv"));
List<String> csvLines = new ArrayList<>();
csvLines.add("classification,path");
csvLines.add("external,no-such-path");
Files.write(repositoryFolders, csvLines, StandardCharsets.UTF_8);
envVars.put("LGTM_REPOSITORY_FOLDERS_CSV", repositoryFolders.toString());
addFile(true, LGTM_SRC, "tst.js");
runTest();
FileUtil8.recursiveDelete(testFolder);
}
/** Hide {@code p} on using {@link DosFileAttributeView} if available; otherwise do nothing. */
private void hide(Path p) throws IOException {
DosFileAttributeView attrs = Files.getFileAttributeView(p, DosFileAttributeView.class);
if (attrs != null) attrs.setHidden(true);
}
@Test
public void hiddenFolders() throws IOException {
Path tst_js = addFile(false, LGTM_SRC, ".DS_Store", "tst.js");
hide(tst_js.getParent());
runTest();
}
@Test
public void hiddenFiles() throws IOException {
Path eslintrc = addFile(true, LGTM_SRC, ".eslintrc.json");
hide(eslintrc);
runTest();
}
@Test
public void noTypescriptExtraction() throws IOException {
envVars.put("LGTM_INDEX_TYPESCRIPT", "none");
addFile(false, LGTM_SRC, "tst.ts");
addFile(false, LGTM_SRC, "sub.js", "tst.ts");
addFile(false, LGTM_SRC, "tst.js.ts");
runTest();
}
@Test
public void includeNonExistentFile() throws IOException {
envVars.put("LGTM_INDEX_INCLUDE", "tst.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeNonExistentFile() throws IOException {
envVars.put("LGTM_INDEX_EXCLUDE", "vendor/leftpad/index.js");
addFile(true, LGTM_SRC, "tst.js");
runTest();
}
@Test
public void minifiedFilesAreExcluded() throws IOException {
addFile(true, LGTM_SRC, "admin.js");
addFile(false, LGTM_SRC, "jquery.min.js");
addFile(false, LGTM_SRC, "lib", "lodash-min.js");
addFile(true, LGTM_SRC, "compute_min.js");
runTest();
}
@Test
public void minifiedFilesCanBeReIncluded() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "include:**/*.min.js\ninclude:**/*-min.js");
addFile(true, LGTM_SRC, "admin.js");
addFile(true, LGTM_SRC, "jquery.min.js");
addFile(true, LGTM_SRC, "lib", "lodash-min.js");
addFile(true, LGTM_SRC, "compute_min.js");
runTest();
}
@Test
public void nodeModulesAreExcluded() throws IOException {
addFile(true, LGTM_SRC, "index.js");
addFile(false, LGTM_SRC, "node_modules", "dep", "main.js");
addFile(false, LGTM_SRC, "node_modules", "dep", "node_modules", "leftpad", "index.js");
runTest();
}
@Test
public void nodeModulesCanBeReincluded() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "include:**/node_modules");
addFile(true, LGTM_SRC, "index.js");
addFile(true, LGTM_SRC, "node_modules", "dep", "main.js");
addFile(true, LGTM_SRC, "node_modules", "dep", "node_modules", "leftpad", "index.js");
runTest();
}
@Test
public void bowerComponentsAreExcluded() throws IOException {
addFile(true, LGTM_SRC, "index.js");
addFile(false, LGTM_SRC, "bower_components", "dep", "main.js");
addFile(false, LGTM_SRC, "bower_components", "dep", "bower_components", "leftpad", "index.js");
runTest();
}
@Test
public void bowerComponentsCanBeReincluded() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "include:**/bower_components");
addFile(true, LGTM_SRC, "index.js");
addFile(true, LGTM_SRC, "bower_components", "dep", "main.js");
addFile(true, LGTM_SRC, "bower_components", "dep", "bower_components", "leftpad", "index.js");
runTest();
}
@Test
public void customExtensions() throws IOException {
envVars.put("LGTM_INDEX_FILETYPES", ".jsm:js\n.soy:html");
addFile(true, FileType.JS, LGTM_SRC, "tst.jsm");
addFile(false, LGTM_SRC, "tstjsm");
addFile(true, FileType.HTML, LGTM_SRC, "tst.soy");
addFile(true, LGTM_SRC, "tst.html");
addFile(true, LGTM_SRC, "tst.js");
runTest();
}
@Test
public void overrideExtension() throws IOException {
envVars.put("LGTM_INDEX_FILETYPES", ".js:typescript");
addFile(true, FileType.TYPESCRIPT, LGTM_SRC, "tst.js");
runTest();
}
@Test
public void invalidFileType() throws IOException {
envVars.put("LGTM_INDEX_FILETYPES", ".jsm:javascript");
try {
runTest();
Assert.fail("expected UserError");
} catch (UserError ue) {
Assert.assertEquals("Invalid file type 'JAVASCRIPT'.", ue.getMessage());
}
}
@Test
public void includeYaml() throws IOException {
addFile(true, LGTM_SRC, "tst.yaml");
addFile(true, LGTM_SRC, "tst.yml");
addFile(true, LGTM_SRC, "tst.raml");
runTest();
}
@Test
public void dontIncludeXmlByDefault() throws IOException {
addFile(false, LGTM_SRC, "tst.xml");
addFile(false, LGTM_SRC, "tst.qhelp");
runTest();
}
@Test
public void includeXml() throws IOException {
envVars.put("LGTM_INDEX_XML_MODE", "all");
addFile(true, LGTM_SRC, "tst.xml");
addFile(false, LGTM_SRC, "tst.qhelp");
runTest();
}
@Test
public void qhelpAsXml() throws IOException {
envVars.put("LGTM_INDEX_FILETYPES", ".qhelp:xml");
addFile(false, LGTM_SRC, "tst.xml");
addFile(true, LGTM_SRC, "tst.qhelp");
runTest();
}
@Test
public void qhelpAsXmlAndAllXml() throws IOException {
envVars.put("LGTM_INDEX_XML_MODE", "all");
envVars.put("LGTM_INDEX_FILETYPES", ".qhelp:xml");
addFile(true, LGTM_SRC, "tst.xml");
addFile(true, LGTM_SRC, "tst.qhelp");
runTest();
}
@Test
public void skipCodeQLDatabases() throws IOException {
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "db/codeql-database.yml");
addFile(false, LGTM_SRC, "db/foo.js");
runTest();
}
@Test
public void hiddenGitHubFoldersAreIncluded() throws IOException {
Path tst_yml = addFile(true, LGTM_SRC, ".github", "workflows", "test.yml");
hide(tst_yml.getParent().getParent());
runTest();
}
@Test
public void hiddenGitHubFoldersCanBeExcluded() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/.github");
Path tst_yml = addFile(false, LGTM_SRC, ".github", "workflows", "test.yml");
hide(tst_yml.getParent().getParent());
runTest();
}
}

View File

@@ -1,46 +0,0 @@
package com.semmle.js.extractor.test;
import org.junit.Test;
/**
* Tests for parsing class properties.
*
* <p>Most tests are automatically derived from the Babylon test suite as described in <code>
* parser-tests/babylon/README.md</code>.
*/
public class ClassPropertiesTests extends ASTMatchingTests {
@Test
public void testASIFailureComputed() {
babylonTest("experimental/class-properties/asi-failure-computed");
}
@Test
public void testASIFailureGenerator() {
babylonTest("experimental/class-properties/asi-failure-generator");
}
@Test
public void testASISuccess() {
babylonTest("experimental/class-properties/asi-success");
}
@Test
public void test43() {
babylonTest("experimental/uncategorised/43");
}
@Test
public void test44() {
babylonTest("experimental/uncategorised/44");
}
@Test
public void test45() {
babylonTest("experimental/uncategorised/45");
}
@Test
public void test46() {
babylonTest("experimental/uncategorised/46");
}
}

View File

@@ -1,71 +0,0 @@
package com.semmle.js.extractor.test;
import org.junit.Test;
/**
* Tests for parsing decorators.
*
* <p>Most tests are automatically derived from the Babylon test suite as described in <code>
* parser-tests/babylon/README.md</code>.
*/
public class DecoratorTests extends ASTMatchingTests {
@Test
public void test33() {
babylonTest("experimental/uncategorised/33");
}
@Test
public void test34() {
babylonTest("experimental/uncategorised/34");
}
@Test
public void test35() {
babylonTest("experimental/uncategorised/35");
}
@Test
public void test36() {
babylonTest("experimental/uncategorised/36");
}
@Test
public void test37() {
babylonTest("experimental/uncategorised/37");
}
@Test
public void test38() {
babylonTest("experimental/uncategorised/38");
}
@Test
public void test39() {
babylonTest("experimental/uncategorised/39");
}
@Test
public void test40() {
babylonTest("experimental/uncategorised/40");
}
@Test
public void test41() {
babylonTest("experimental/uncategorised/41");
}
@Test
public void test42() {
babylonTest("experimental/uncategorised/42");
}
@Test
public void test49() {
babylonTest("experimental/uncategorised/49");
}
@Test
public void test62() {
babylonTest("experimental/uncategorised/62");
}
}

View File

@@ -1,42 +0,0 @@
package com.semmle.js.extractor.test;
import com.semmle.jcorn.Options;
import org.junit.Test;
/**
* Tests for parsing export extensions.
*
* <p>Most tests are automatically derived from the Babylon test suite as described in <code>
* parser-tests/babylon/README.md</code>.
*/
public class ExportExtensionsTests extends ASTMatchingTests {
@Override
protected void babylonTest(String dir) {
babylonTest(dir, new Options().esnext(true).sourceType("module"));
}
@Test
public void test50() {
babylonTest("experimental/uncategorised/50");
}
@Test
public void test51() {
babylonTest("experimental/uncategorised/51");
}
@Test
public void test52() {
babylonTest("experimental/uncategorised/52");
}
@Test
public void test53() {
babylonTest("experimental/uncategorised/53");
}
@Test
public void test54() {
babylonTest("experimental/uncategorised/54");
}
}

View File

@@ -1,26 +0,0 @@
package com.semmle.js.extractor.test;
import org.junit.Test;
/**
* Tests for parsing <code>function.sent</code>.
*
* <p>Most tests are automatically derived from the Babylon test suite as described in <code>
* parser-tests/babylon/README.md</code>.
*/
public class FunctionSentTests extends ASTMatchingTests {
@Test
public void testInsideFunction() {
babylonTest("experimental/function-sent/inside-function");
}
@Test
public void testInsideGenerator() {
babylonTest("experimental/function-sent/inside-generator");
}
@Test
public void testInvalidProperty() {
babylonTest("experimental/function-sent/invalid-property");
}
}

View File

@@ -1,95 +0,0 @@
package com.semmle.js.extractor.test;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.semmle.jcorn.SyntaxError;
import com.semmle.jcorn.jsx.JSXOptions;
import com.semmle.jcorn.jsx.JSXParser;
import com.semmle.js.ast.AST2JSON;
import com.semmle.js.ast.Program;
import com.semmle.util.files.FileUtil;
import com.semmle.util.io.WholeIO;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
/**
* Tests for the JSX parser, automatically generated from the acorn-jsx test suite as described in
* <code>parser-tests/jcorn-jsx/README.md</code>.
*/
@RunWith(Parameterized.class)
public class JSXTests extends ASTMatchingTests {
private static final File BASE = new File("parser-tests/jcorn-jsx").getAbsoluteFile();
@Parameters(name = "{0}")
public static Iterable<Object[]> tests() {
List<Object[]> testData = new ArrayList<Object[]>();
// iterate over all tests
for (File test : BASE.listFiles(FileUtil.extensionFilter(true, ".js"))) {
String testName = FileUtil.basename(test);
JSXOptions options = new JSXOptions().allowNamespacedObjects(false);
File optionsFile = new File(test.getParentFile(), testName + ".options.json");
if (optionsFile.exists()) {
JsonObject optionsJson =
new JsonParser().parse(new WholeIO().strictread(optionsFile)).getAsJsonObject();
if (optionsJson.has("allowNamespacedObjects"))
options =
options.allowNamespacedObjects(
optionsJson.get("allowNamespacedObjects").getAsBoolean());
if (optionsJson.has("allowNamespaces"))
options = options.allowNamespaces(optionsJson.get("allowNamespaces").getAsBoolean());
}
JsonObject expectedAST;
File expectedASTFile = new File(test.getParentFile(), testName + ".ast");
if (expectedASTFile.exists())
expectedAST =
new JsonParser().parse(new WholeIO().strictread(expectedASTFile)).getAsJsonObject();
else expectedAST = null;
String expectedFailure;
File expectedFailureFile = new File(test.getParentFile(), testName + ".fail");
if (expectedFailureFile.exists())
expectedFailure = new WholeIO().strictread(expectedFailureFile).trim();
else expectedFailure = null;
testData.add(new Object[] {testName, options, expectedAST, expectedFailure});
}
return testData;
}
private final String testName;
private final JSXOptions options;
private final JsonObject expectedAST;
private final String expectedFailure;
public JSXTests(
String testName, JSXOptions options, JsonObject expectedAST, String expectedFailure) {
this.testName = testName;
this.options = options;
this.expectedAST = expectedAST;
this.expectedFailure = expectedFailure;
}
@Test
public void runtest() {
File inputFile = new File(BASE, testName + ".js");
String input = new WholeIO().strictread(inputFile);
try {
Program actualProgram = new JSXParser(options, input, 0).parse();
JsonElement actualAST = AST2JSON.convert(actualProgram);
assertMatch("<root>", expectedAST, actualAST);
} catch (SyntaxError e) {
Assert.assertEquals(expectedFailure, e.getMessage());
}
}
}

View File

@@ -1,214 +0,0 @@
package com.semmle.js.extractor.test;
import com.semmle.js.ast.Node;
import com.semmle.js.extractor.ExtractionMetrics;
import com.semmle.js.extractor.ExtractorConfig;
import com.semmle.js.extractor.ExtractorConfig.SourceType;
import com.semmle.js.extractor.NodeJSDetector;
import com.semmle.js.parser.JSParser;
import com.semmle.js.parser.JSParser.Result;
import org.junit.Assert;
import org.junit.Test;
public class NodeJSDetectorTests {
private static final ExtractorConfig CONFIG = new ExtractorConfig(false);
private void isNodeJS(String src, boolean expected) {
Result res = JSParser.parse(CONFIG, SourceType.SCRIPT, src, new ExtractionMetrics());
Node ast = res.getAST();
Assert.assertNotNull(ast);
Assert.assertTrue(NodeJSDetector.looksLikeNodeJS(ast) == expected);
}
@Test
public void testBareRequire() {
isNodeJS("require('fs');", true);
}
@Test
public void testRequireInit() {
isNodeJS("var fs = require('fs');", true);
}
@Test
public void testRequireInit2() {
isNodeJS("var foo, fs = require('fs');", true);
}
@Test
public void testDirective() {
isNodeJS("\"use strict\"; require('fs');", true);
}
@Test
public void testComment() {
isNodeJS(
"/** My awesome package.\n"
+ "* (C) me.\n"
+ "*/\n"
+ "\n"
+ "var isArray = require(\"isArray\");",
true);
}
@Test
public void testInitialExport() {
isNodeJS("exports.foo = 0; console.log('Hello, world!');", true);
}
@Test
public void testInitialModuleExport() {
isNodeJS("module.exports.foo = 0; console.log('Hello, world!');", true);
}
@Test
public void testInitialBulkExport() {
isNodeJS("module.exports = {}; console.log('Hello, world!');", true);
}
@Test
public void testTrailingExport() {
isNodeJS("console.log('Hello, world!'); exports.foo = 0;", true);
}
@Test
public void testTrailingModuleExport() {
isNodeJS("console.log('Hello, world!'); module.exports.foo = 0;", true);
}
@Test
public void testTrailingBulkExport() {
isNodeJS("console.log('Hello, world!'); module.exports = {};", true);
}
@Test
public void testInitialNestedExport() {
isNodeJS("mystuff = module.exports = {}; mystuff.foo = 0;", true);
}
@Test
public void testInitialNestedExportInit() {
isNodeJS("var mystuff = module.exports = exports = {}; mystuff.foo = 0;", true);
}
@Test
public void testTrailingRequire() {
isNodeJS("console.log('Hello, world!'); var fs = require('fs');", true);
}
@Test
public void testSandwichedExport() {
isNodeJS("console.log('Hello'); exports.foo = 0; console.log('world!');", true);
}
@Test
public void umd() {
isNodeJS(
"(function(define) {\n"
+ " define(function (require, exports, module) {\n"
+ " var b = require('b');\n"
+ " return function () {};\n"
+ " });\n"
+ "}(\n"
+ " typeof module === 'object' && module.exports && typeof define !== 'function' ?\n"
+ " function (factory) { module.exports = factory(require, exports, module); } :\n"
+ " define\n"
+ "));",
false);
}
@Test
public void testRequirePropAccess() {
isNodeJS("var foo = require('./b').foo;", true);
}
@Test
public void testReExport() {
isNodeJS("module.exports = require('./e');", true);
}
@Test
public void testSeparateVar() {
isNodeJS("var fs; fs = require('fs');", true);
}
@Test
public void testLet() {
isNodeJS("let fs = require('fs');", true);
}
@Test
public void testSeparateLet() {
isNodeJS("let fs; fs = require('fs');", true);
}
@Test
public void testConst() {
isNodeJS("const fs = require('fs');", true);
}
@Test
public void testIife() {
isNodeJS(";(function() { require('fs'); })()", true);
}
@Test
public void testIife2() {
isNodeJS("!function() { require('fs'); }()", true);
}
@Test
public void testUMD() {
isNodeJS("(function(require) { require('fs'); })(myRequire);", false);
}
@Test
public void amdefine() {
isNodeJS(
"if (typeof define !== 'function') define = require('amdefine')(module, require);", true);
}
@Test
public void requireAndReadProp() {
isNodeJS("var readFileSync = require('fs').readFileSync;", true);
}
@Test
public void toplevelAMDRequire() {
isNodeJS("require(['foo'], function(foo) { });", false);
}
@Test
public void requireInTry() {
isNodeJS(
"var fs;"
+ "try {"
+ " fs = require('graceful-fs');"
+ "} catch(e) {"
+ " fs = require('fs');"
+ "}",
true);
}
@Test
public void requireInIf() {
isNodeJS(
"var fs;"
+ "if (useGracefulFs) {"
+ " fs = require('graceful-fs');"
+ "} else {"
+ " fs = require('fs');"
+ "}",
true);
}
@Test
public void requireAndCall() {
isNodeJS("var foo = require('foo')();", true);
}
@Test
public void requireAndCallMethod() {
isNodeJS("var foo = require('foo').bar();", true);
}
}

View File

@@ -1,44 +0,0 @@
package com.semmle.js.extractor.test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.semmle.jcorn.ESNextParser;
import com.semmle.jcorn.Options;
import com.semmle.jcorn.SyntaxError;
import com.semmle.js.ast.ExpressionStatement;
import com.semmle.js.ast.Literal;
import com.semmle.js.ast.Program;
import org.junit.Test;
public class NumericSeparatorTests {
private void test(String src, Integer numVal) {
try {
Program p = new ESNextParser(new Options().esnext(true), src, 0).parse();
assertNotNull(numVal);
assertEquals(1, p.getBody().size());
assertTrue(p.getBody().get(0) instanceof ExpressionStatement);
ExpressionStatement exprStmt = (ExpressionStatement) p.getBody().get(0);
assertTrue(exprStmt.getExpression() instanceof Literal);
assertEquals(numVal.longValue(), ((Literal) exprStmt.getExpression()).getValue());
} catch (SyntaxError e) {
assertNull(e.toString(), numVal);
}
}
@Test
public void test() {
test("0b_", null);
test("0b0_1", 0b01);
test("0B0_1", 0b01);
test("0b0_10", 0b010);
test("0B0_10", 0b010);
test("0b01_0", 0b010);
test("0B01_0", 0b010);
test("0b01_00", 0b0100);
test("0B01_00", 0b0100);
test("0b0__0", null);
test("0b0_", null);
}
}

View File

@@ -1,59 +0,0 @@
package com.semmle.js.extractor.test;
import com.semmle.jcorn.CustomParser;
import com.semmle.jcorn.Options;
import com.semmle.jcorn.SyntaxError;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests for parsing rest/spread properties.
*
* <p>Most tests are automatically derived from the Babylon test suite as described in <code>
* parser-tests/babylon/README.md</code>.
*/
public class ObjectRestSpreadTests extends ASTMatchingTests {
private void testFail(String input, String msg) {
try {
new CustomParser(new Options().esnext(true), input, 0).parse();
Assert.fail("Expected syntax error, but parsing succeeded.");
} catch (SyntaxError e) {
Assert.assertEquals(msg, e.getMessage());
}
}
@Test
public void test9() {
babylonTest("experimental/uncategorised/9");
}
@Test
public void test10() {
babylonTest("experimental/uncategorised/10");
}
@Test
public void test11() {
babylonTest("experimental/uncategorised/11");
}
@Test
public void test12() {
babylonTest("experimental/uncategorised/12");
}
@Test
public void test13() {
babylonTest("experimental/uncategorised/13");
}
@Test
public void testObjectRestSpread() {
babylonTest("harmony/arrow-functions/object-rest-spread");
}
@Test
public void testFail() {
testFail("({...'hi'} = {})", "Assigning to rvalue (1:5)");
}
}

View File

@@ -1,18 +0,0 @@
package com.semmle.js.extractor.test;
import com.semmle.jcorn.Options;
import com.semmle.jcorn.Parser;
import com.semmle.util.io.WholeIO;
import java.io.File;
import java.nio.charset.StandardCharsets;
import org.junit.Test;
public class RobustnessTests {
@Test
public void letLookheadTest() {
File test = new File("parser-tests/robustness/letLookahead.js");
String src = new WholeIO(StandardCharsets.UTF_8.name()).strictread(test);
new Parser(new Options(), src, 0).parse();
}
}

View File

@@ -1,195 +0,0 @@
package com.semmle.js.extractor.test;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.semmle.js.extractor.ExtractorState;
import com.semmle.js.extractor.Main;
import com.semmle.util.data.Pair;
import com.semmle.util.data.StringUtil;
import com.semmle.util.extraction.ExtractorOutputConfig;
import com.semmle.util.io.WholeIO;
import com.semmle.util.process.Env;
import com.semmle.util.srcarchive.DummySourceArchive;
import com.semmle.util.trap.ITrapWriterFactory;
import com.semmle.util.trap.TrapWriter;
import com.semmle.util.trap.pathtransformers.ProjectLayoutTransformer;
import java.io.File;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class TrapTests {
private static final File BASE = new File("tests").getAbsoluteFile();
@Parameters(name = "{0}:{1}")
public static Iterable<Object[]> tests() {
List<Object[]> testData = new ArrayList<Object[]>();
// iterate over all test groups
List<String> testGroups = Arrays.asList(BASE.list());
testGroups.sort(Comparator.naturalOrder());
for (String testgroup : testGroups) {
File root = new File(BASE, testgroup);
if (root.isDirectory()) {
// check for options.json file and process it if it exists
List<String> options = new ArrayList<String>();
File optionsFile = new File(root, "options.json");
if (optionsFile.exists()) {
JsonParser p = new JsonParser();
JsonObject jsonOpts = p.parse(new WholeIO().strictread(optionsFile)).getAsJsonObject();
for (Entry<String, JsonElement> e : jsonOpts.entrySet()) {
JsonElement v = e.getValue();
if (v instanceof JsonPrimitive) {
JsonPrimitive pv = (JsonPrimitive) v;
if (pv.isBoolean() && pv.getAsBoolean()) {
options.add("--" + e.getKey());
} else {
options.add("--" + e.getKey());
options.add(pv.getAsString());
}
} else {
Assert.assertTrue(v instanceof JsonArray);
JsonArray a = (JsonArray) v;
for (JsonElement elt : a) {
options.add("--" + e.getKey());
options.add(elt.getAsString());
}
}
}
}
File inputDir = new File(root, "input");
File jsconfigFile = new File(inputDir, "tsconfig.json");
if (jsconfigFile.exists()) {
// create a single test with all files in the group
testData.add(new Object[] {testgroup, "tsconfig", new ArrayList<String>(options)});
} else {
// create isolated tests for each input file in the group
List<String> tests = Arrays.asList(inputDir.list());
tests.sort(Comparator.naturalOrder());
for (String testfile : tests) {
testData.add(new Object[] {testgroup, testfile, new ArrayList<String>(options)});
}
}
}
}
return testData;
}
private final String testgroup, testname;
private final List<String> options;
private static String userDir;
private static ExtractorState state;
public TrapTests(String testgroup, String testname, List<String> options) {
this.testgroup = testgroup;
this.testname = testname;
this.options = options;
}
@BeforeClass
public static void saveUserDir() {
userDir = System.getProperty("user.dir");
}
@BeforeClass
public static void setupState() {
state = new ExtractorState();
}
@AfterClass
public static void restoreUserDir() {
System.setProperty("user.dir", userDir);
}
@Test
public void runtest() {
state.reset();
File testdir = new File(BASE, testgroup);
File inputDir = new File(testdir, "input");
File inputFile = new File(inputDir, testname);
final File outputDir = new File(testdir, "output/trap");
System.setProperty("user.dir", inputFile.getParent());
options.add("--extract-program-text");
options.add("--quiet");
if (new File(inputDir, "tsconfig.json").exists()) {
// Use full extractor on entire directory.
options.add("--typescript-full");
options.add(inputDir.getAbsolutePath());
} else {
// Use basic extractor on a single file.
options.add("--typescript");
options.add(inputFile.getAbsolutePath());
}
final List<Pair<String, String>> expectedVsActual = new ArrayList<Pair<String, String>>();
Main main =
new Main(
new ExtractorOutputConfig(
new ITrapWriterFactory() {
@Override
public TrapWriter mkTrapWriter(final File f) {
final StringWriter sw = new StringWriter();
ProjectLayoutTransformer transformer =
new ProjectLayoutTransformer(new File(BASE, "project-layout"));
return new TrapWriter(sw, transformer) {
@Override
public void close() {
super.close();
// convert to and from UTF-8 to mimick treatment of unencodable characters
byte[] actual_utf8_bytes = StringUtil.stringToBytes(sw.toString());
String actual = new String(actual_utf8_bytes, Charset.forName("UTF-8"));
File trap = new File(outputDir, f.getName() + ".trap");
boolean replaceExpectedOutput = false;
if (replaceExpectedOutput) {
System.out.println("Replacing expected output for " + trap);
new WholeIO().strictwrite(trap, actual);
}
String expected = new WholeIO().strictreadText(trap);
expectedVsActual.add(Pair.make(expected, actual));
}
@Override
public void addTuple(String tableName, Object... values) {
if ("extraction_data".equals(tableName)
|| "extraction_time".equals(tableName)) {
// ignore non-deterministic tables
return;
}
super.addTuple(tableName, values);
}
};
}
@Override
public File getTrapFileFor(File f) {
return null;
}
},
new DummySourceArchive()),
state);
main.run(options.toArray(new String[options.size()]));
for (Pair<String, String> p : expectedVsActual) Assert.assertEquals(p.fst(), p.snd());
}
}