Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run JS API unit tests and integration tests in the Gradle build #4988

Merged
merged 32 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b048f9c
Hacked together tests
niloc132 Apr 14, 2023
c05823a
Tests build, but do not pass
niloc132 Apr 19, 2023
30d1df4
bad idea: opt in to a huge payload from the server - should instead
niloc132 Sep 28, 2023
47a68cd
quick hack to decrease treemap costs - fix with non-random loading
niloc132 Sep 28, 2023
ea53236
spotless for ci
niloc132 Oct 23, 2023
e8ddd50
fix regression in formatting longs
niloc132 Nov 2, 2023
5f8cb54
integration tests run, fail
niloc132 Nov 2, 2023
a855985
Tests are passing in manual mode, except filter setup
niloc132 Nov 3, 2023
06a17a5
Tidy up tests, disable filter tests for now
niloc132 Nov 3, 2023
354fd48
Add additional integration tests
niloc132 Nov 6, 2023
d8ad542
null values test
niloc132 Nov 6, 2023
9df4859
POtential totals table test
niloc132 Nov 6, 2023
d9da1ca
Attempt at reusing code from gwt-core test
niloc132 Dec 21, 2023
6e6326d
Nearly working tests from gradle, with TODOs
niloc132 Dec 23, 2023
92e6f3a
Checkpoint commit, gwt test tasks pass, check fails
niloc132 Dec 27, 2023
37639a0
Dirty hack to avoid esoco's assumption that compile must run after test
niloc132 Dec 27, 2023
03939d9
Don't run gwt tests with in quick
niloc132 Dec 27, 2023
0d15ac3
Remove unused build file, raise timeout on a test
niloc132 Dec 27, 2023
49b9b95
Merge branch 'main' into js-flight
niloc132 Dec 27, 2023
0d02ff3
Clean up readability, use a registry image
niloc132 Dec 27, 2023
dd54ea8
tidy things up to working in CI
niloc132 Dec 27, 2023
e16cbb7
Revert SubscriptionTableData, WorkerConnection
niloc132 Dec 27, 2023
4af1341
Final cleanup (?)
niloc132 Dec 27, 2023
43a102f
Parameterize port and timeouts, other cleanup
niloc132 Jan 2, 2024
2bab42b
Rework networking so hopefully docker-desktop behaves
niloc132 Jan 2, 2024
d61c783
Add healthcheck, cleanup
niloc132 Jan 2, 2024
a11687d
Clean up forked test runner
niloc132 Jan 2, 2024
e03f3e7
Inline more wd runstyle contents
niloc132 Jan 2, 2024
ab0bb30
Change up timeouts a bit
mofojed Jan 3, 2024
4413dcf
Increase a few more timeouts and make them unique
niloc132 Jan 3, 2024
a0c4f0d
Add missing basic column types
niloc132 Jan 3, 2024
713007d
Fix selenium port mapping
niloc132 Jan 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 9 additions & 21 deletions buildSrc/src/main/groovy/GwtTools.groovy
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import de.esoco.gwt.gradle.GwtLibPlugin
import de.esoco.gwt.gradle.GwtPlugin
import de.esoco.gwt.gradle.extension.GwtExtension
import de.esoco.gwt.gradle.task.GwtCheckTask
import de.esoco.gwt.gradle.task.GwtCompileTask
import groovy.transform.CompileStatic
import org.gradle.api.Project
Expand Down Expand Up @@ -36,6 +37,13 @@ class GwtTools {
GwtCompileTask gwtc ->
applyModuleSettings p, gwtc, module,description
}
// This GWT plugin will fail if tests are run after compilation, instead
// we suppress running the test at all, and ensure that it doesn't check
// if it even can be run until after compile finishes.
p.tasks.withType(GwtCheckTask).configureEach {t ->
t.mustRunAfter(p.tasks.withType(GwtCompileTask))
t.onlyIf { false }
}

return ext
}
Expand Down Expand Up @@ -70,8 +78,6 @@ class GwtTools {
gwtDev && gwtc.doFirst {
gwtc.logger.quiet('Running in gwt dev mode; saving source to {}/dh/src', extras)
}

p.tasks.findByName('gwtCheck')?.enabled = false
}

static void applyDefaults(Project p, GwtExtension gwt, boolean compile = false) {
Expand Down Expand Up @@ -110,31 +116,13 @@ class GwtTools {
maxHeapSize = "1024m"
minHeapSize = "512m"
}

gwt.dev.with {
/** The ip address of the code server. */
bindAddress = "127.0.0.1"
/** The port where the code server will run. */
port = 9876
/** Specifies Java source level ("1.6", "1.7").
sourceLevel = "1.8"
/** The level of logging detail (ERROR, WARN, INFO, TRACE, DEBUG, SPAM, ALL) */
logLevel = "INFO"
/** Emit extra information allow chrome dev tools to display Java identifiers in many placesinstead of JavaScript functions. (NONE, ONLY_METHOD_NAME, ABBREVIATED, FULL) */
methodNameDisplayMode = "NONE"
/** Where to write output files */
war = warPath
// extraArgs = ["-firstArgument", "-secondArgument"]
}


}
}

static void addGeneratedSources(Project project, GwtCompileTask gwtc) {
if (project.configurations.getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME).dependencies) {
(gwtc.src as ConfigurableFileCollection).from(
(project.tasks.getByName(JavaPlugin.COMPILE_JAVA_TASK_NAME) as JavaCompile).options.annotationProcessorGeneratedSourcesDirectory
(project.tasks.getByName(JavaPlugin.COMPILE_JAVA_TASK_NAME) as JavaCompile).options.generatedSourceOutputDirectory
)
}
project.configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME).allDependencies.withType(ProjectDependency)*.dependencyProject*.each {
Expand Down
3 changes: 3 additions & 0 deletions docker/registry/selenium/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
plugins {
id 'io.deephaven.project.register'
}
3 changes: 3 additions & 0 deletions docker/registry/selenium/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
io.deephaven.project.ProjectType=DOCKER_REGISTRY
deephaven.registry.imageName=selenium/standalone-firefox:4.16.1-20231219
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the other imageName are more open-ended so that the imageId can be bumped w/ bumpImage task. Is there a reason this tag was chosen?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Selenium client has historically been firmly tied to a single release of the browser+driver version, so it rarely will work to update this to "latest" without finding out what version that happens to be, and then update the test dependencies to match that specific version. This way, the version number is clear in both places.

deephaven.registry.imageId=selenium/standalone-firefox@sha256:a405fe92b3ce5d7eb31a07e1f99be3d628fdc0e5bdc81febd8dc11786edef024
33 changes: 0 additions & 33 deletions gradle/web-gwt-test.gradle

This file was deleted.

110 changes: 108 additions & 2 deletions web/client-api/client-api.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import com.bmuschko.gradle.docker.tasks.container.DockerCreateContainer
import com.bmuschko.gradle.docker.tasks.container.DockerRemoveContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStartContainer
import io.deephaven.tools.docker.WaitForHealthyContainer

plugins {
id 'io.deephaven.project.register'
id 'io.deephaven.deephaven-in-docker'
}

evaluationDependsOn(Docker.registryProject('selenium'))

apply from: "$rootDir/gradle/web-client.gradle"

configurations {
js
dts
typescriptDoclet
testImplementation.extendsFrom junit
}

dependencies {
Expand All @@ -20,6 +29,8 @@ dependencies {
implementation 'com.vertispan.nio:gwt-nio:1.0-alpha-1'

js project(path: ':proto:raw-js-openapi', configuration: 'js')

testImplementation 'org.seleniumhq.selenium:selenium-remote-driver:4.16.1'
}
Classpaths.inheritElemental(project, 'elemental2-core', 'implementation')
Classpaths.inheritElemental(project, 'elemental2-promise', 'implementation')
Expand Down Expand Up @@ -57,6 +68,101 @@ artifacts {
}
}

project.tasks.getByName('quick').dependsOn project.tasks.withType(de.esoco.gwt.gradle.task.GwtCompileTask)
def gwtUnitTest = tasks.register('gwtUnitTest', Test) { t ->
t.systemProperties = [
'gwt.args': ['-runStyle HtmlUnit',
'-ea',
'-style PRETTY',
"-war ${layout.buildDirectory.dir('unitTest-war').get().asFile.absolutePath}"
].join(' '),
'gwt.persistentunitcachedir': layout.buildDirectory.dir('unitTest-unitCache').get().asFile.absolutePath,
]
t.include '**/ClientUnitTestSuite.class'
t.useJUnit()
t.scanForTestClasses = false
}

// start a grpc-api server
String randomSuffix = UUID.randomUUID().toString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing this? Is this safe to do? Seems like this would mess up gradle caching?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would yes, but we are doing this all over for naming docker containers, so that you don't accidentally collide in parallel runs, or separate checkouts, etc. Since this is just for tests, we're not concerned with "oh these tests passed last time, so I'm not even going to run them this time" for integration tests.

deephavenDocker {
envVars.set([
'START_OPTS':'-Xmx512m -DAuthHandlers=io.deephaven.auth.AnonymousAuthenticationHandler'
])
containerName.set "dh-server-for-js-${randomSuffix}"
networkName.set "js-test-network-${randomSuffix}"
}

def seleniumContainerId = "selenium-${randomSuffix}"
def seleniumPort
if (!hasProperty('selenium.port')) {
seleniumPort = '4444'
} else {
seleniumPort = project.getProperty('selenium.port')
}

def createSelenium = tasks.register('createSelenium', DockerCreateContainer) { t ->
t.dependsOn(Docker.registryTask(project, 'selenium'), deephavenDocker.startTask)
t.targetImageId('deephaven/selenium:local-build')
t.containerName.set(seleniumContainerId)
// Advised by the selenium documentation
t.hostConfig.shmSize.set(2L * 1024 * 1024 * 1024)

apply from: "$rootDir/gradle/web-gwt-test.gradle"
// Add our own healthcheck to confirm the container starts fully
t.healthCheck.cmd.set(['curl http://localhost:4444/wd/hub/status || exit 1'])

// This provides a hostname that can be referenced from inside the docker container to access the host
// OS, and connect to the test server.
t.hostConfig.extraHosts.add('host.docker.internal:host-gateway')
t.hostConfig.portBindings.set(["$seleniumPort:4444"])
t.hostConfig.network.set(deephavenDocker.networkName.get())
}
def startSelenium = tasks.register('startSelenium', DockerStartContainer) {t ->
t.dependsOn(createSelenium)
t.containerId.set(seleniumContainerId)
}
def seleniumHealthy = project.tasks.register('seleniumHealthy', WaitForHealthyContainer) { task ->
task.dependsOn startSelenium

task.awaitStatusTimeout.set 120
task.checkInterval.set 100
Comment on lines +126 to +127
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note - really confusing that awaitStatusTimeout is in seconds, and checkInterval is in ms.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed... can go back and rewrite those, but I vote not today. This is code taken from the upstream docker-gradle plugin, and we didn't customize it further.


task.containerId.set(seleniumContainerId)
}
def stopSelenium = project.tasks.register('stopSelenium', DockerRemoveContainer) { task ->
task.dependsOn startSelenium
task.targetContainerId seleniumContainerId
task.force.set true
task.removeVolumes.set true
}

def gwtIntegrationTest = tasks.register('gwtIntegrationTest', Test) { t ->
t.dependsOn(deephavenDocker.portTask, seleniumHealthy)
t.finalizedBy(deephavenDocker.endTask, stopSelenium)
doFirst {
def webdriverUrl = "http://localhost:${seleniumPort}/"
t.systemProperty('gwt.args', ["-runStyle io.deephaven.web.junit.RunStyleRemoteWebDriver:${webdriverUrl}?firefox",
'-ea',
'-style PRETTY',
"-setProperty dh.server=http://${deephavenDocker.containerName.get()}:10000",
"-war ${layout.buildDirectory.dir('integrationTest-war').get().asFile.absolutePath}"
].join(' '))
t.classpath += tasks.getByName('gwtCompile').src
}
t.finalizedBy(deephavenDocker.endTask)
t.systemProperties = [
'gwt.persistentunitcachedir':layout.buildDirectory.dir('integrationTest-unitCache').get().asFile.absolutePath,
'webdriver.test.host':'host.docker.internal',
]
t.include '**/ClientIntegrationTestSuite.class'
t.useJUnit()
t.scanForTestClasses = false
}

tasks.named('check').configure {
dependsOn(gwtUnitTest, gwtIntegrationTest)
}

test {
// Configure jvm-only tests to not run any GWT-only tests
exclude '**/*TestGwt.class', '**/*TestSuite.class'
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import io.deephaven.web.client.api.batch.TableConfig;
import io.deephaven.web.client.api.console.JsVariableType;
import io.deephaven.web.client.api.filter.FilterCondition;
import io.deephaven.web.client.api.filter.FilterValue;
import io.deephaven.web.client.api.input.JsInputTable;
import io.deephaven.web.client.api.lifecycle.HasLifecycle;
import io.deephaven.web.client.api.state.StateCache;
Expand All @@ -71,6 +72,8 @@
import io.deephaven.web.shared.fu.JsProvider;
import io.deephaven.web.shared.fu.JsRunnable;
import io.deephaven.web.shared.fu.RemoverFn;
import javaemul.internal.annotations.DoNotAutobox;
import jsinterop.annotations.JsIgnore;
import jsinterop.annotations.JsMethod;
import jsinterop.annotations.JsNullable;
import jsinterop.annotations.JsOptional;
Expand Down Expand Up @@ -594,6 +597,11 @@ public JsArray<FilterCondition> applyFilter(FilterCondition[] filter) {
@TsUnion
@JsType(name = "?", namespace = JsPackage.GLOBAL, isNative = true)
public interface CustomColumnArgUnionType {
@JsOverlay
static CustomColumnArgUnionType of(@DoNotAutobox Object value) {
return Js.cast(value);
}

@JsOverlay
default boolean isString() {
return (Object) this instanceof String;
Expand Down Expand Up @@ -1762,8 +1770,8 @@ public void handleSnapshot(TableTicket handle, TableSnapshot snapshot) {
viewportRows.size());
}


protected void processSnapshot() {
@JsIgnore
public void processSnapshot() {
niloc132 marked this conversation as resolved.
Show resolved Hide resolved
try {
if (debounce == null) {
JsLog.debug("Skipping snapshot b/c debounce is null");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.deephaven.web;

import com.google.gwt.junit.tools.GWTTestSuite;
import io.deephaven.web.client.api.NullValueTestGwt;
import io.deephaven.web.client.api.subscription.ConcurrentTableTestGwt;
import io.deephaven.web.client.api.TableManipulationTestGwt;
import io.deephaven.web.client.api.subscription.ViewportTestGwt;
import junit.framework.Test;
import junit.framework.TestSuite;

public class ClientIntegrationTestSuite extends GWTTestSuite {
public static Test suite() {
TestSuite suite = new TestSuite("Deephaven JS API Integration Test Suite");

// This test doesn't actually talk to the server, but it requires the dh-internal library be available.
// Disabled for now, we don't have good toString on the FilterCondition/FilterValue types.
// suite.addTestSuite(FilterConditionTestGwt.class);

// Actual integration tests
suite.addTestSuite(ViewportTestGwt.class);
suite.addTestSuite(TableManipulationTestGwt.class);
suite.addTestSuite(ConcurrentTableTestGwt.class);
suite.addTestSuite(NullValueTestGwt.class);

// Unfinished:
// suite.addTestSuite(TotalsTableTestGwt.class);

return suite;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
package io.deephaven.web;

import com.google.gwt.junit.tools.GWTTestSuite;
import io.deephaven.web.client.api.filter.FilterConditionTestGwt;
import io.deephaven.web.client.api.i18n.JsDateTimeFormatTestGwt;
import io.deephaven.web.client.api.i18n.JsNumberFormatTestGwt;
import junit.framework.Test;
import junit.framework.TestSuite;

public class ApiTestSuite extends GWTTestSuite {
/**
* Tests that require a browser environment to run, but do not require the server.
*/
public class ClientUnitTestSuite extends GWTTestSuite {
public static Test suite() {
TestSuite suite = new TestSuite("Deephaven Web API Test Suite");
suite.addTestSuite(FilterConditionTestGwt.class);

TestSuite suite = new TestSuite("Deephaven JS API Unit Test Suite");
suite.addTestSuite(JsDateTimeFormatTestGwt.class);
suite.addTestSuite(JsNumberFormatTestGwt.class);
return suite;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<module>
<inherits name="io.deephaven.web.DeephavenApi" />

<!-- restore the default linker that the test tooling expects -->
<add-linker name="xsiframe" />

<!-- define url to load dh-internal from, grpc server to connect to -->
<define-configuration-property name="dh.server" is-multi-valued="false"/>
</module>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<module>
<inherits name="io.deephaven.web.DeephavenApi" />

<!-- restore the default linker that the test tooling expects -->
<add-linker name="xsiframe" />


<!-- define url to load dh-internal from, grpc server to connect to -->
<define-configuration-property name="dh.server" is-multi-valued="false"/>
<!-- set a value so that the app can compile at all -->
<set-configuration-property name="dh.server" value="notset" />
</module>
Loading
Loading