Coder Social home page Coder Social logo

gordon's Introduction

Gordon

Latest release

Gordon is an Android instrumentation test runner designed for speed, simplicity, and reliability. We built it because neither Spoon nor Fork were fast enough nor reliable enough for us, and in attempts to fork those libraries we found them to be too old and complicated to be worth modifying. So we wrote Gordon from the ground up, using modern Gradle functionality and Kotlin coroutines.

Key features

See also:

Better Android Instrumentation Testing with Gordon on ProAndroidDev

Setup

With Gradle plugins block

settings.gradle.kts of your root project

pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }

    plugins {
        id("com.banno.gordon") version "$gordonVersion"
    }
}

build.gradle.kts of any modules for which you want to run tests using Gordon

plugins {
    id("com.banno.gordon")
}

With old Gradle plugins syntax

build.gradle of your root project

buildscript {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }

    dependencies {
        classpath "com.banno.gordon:gordon-plugin:$gordonVersion"
    }
}

build.gradle of any modules for which you want to run tests using Gordon

apply plugin: "com.banno.gordon"

buildSrc

If you have a buildSrc module, you may also need to add the dependency there if you get aapt2 errors.

build.gradle.kts of your buildSrc module

repositories {
    gradlePluginPortal()
    google()
    mavenCentral()
}

dependencies {
    implementation("com.banno.gordon:gordon-plugin:$gordonVersion")
}

Configuring

build.gradle.kts of any module for which you've applied Gordon

import com.banno.gordon.PoolingStrategy

gordon {
    // Default is PoolingStrategy.PoolPerDevice
    poolingStrategy.set(PoolingStrategy.PhonesAndTablets)
    //or
    poolingStrategy.set(
        PoolingStrategy.Manual(
            mapOf(
                "poolOne" to setOf("deviceSerial1", "deviceSerial2"),
                "poolTwo" to setOf("deviceSerial3", "deviceSerial4")
            )
        )
    )

    // Default is unset (`-1`) - to use "tablet" characteristic instead of size
    tabletShortestWidthDp(720)

    // Default is 0
    retryQuota.set(2)

    // Default is 120_000 (2 minutes)
    installTimeoutMillis.set(180_000)

    // Default is 120_000 (2 minutes)
    testTimeoutMillis.set(60_000)

    // Default is no filter
    testFilter.set("ExampleTest.runThisMethod,RunThisWholeTestClass,com.example.runthispackage,com.example.RunTestsWithThisAnnotation")

    // Default is false - to ignore devices that failed during artifacts installation, may be useful with large number of devices and SinglePool strategy
    ignoreProblematicDevices.set(true)
}
Groovy build.gradle example for PoolingStrategy
import com.banno.gordon.PoolingStrategy

gordon {
    poolingStrategy.set(PoolingStrategy.PhonesAndTablets.INSTANCE)
    //or
    poolingStrategy.set(
            new PoolingStrategy.Manual(
                    [
                            "poolOne": ["deviceSerial1", "deviceSerial2"].toSet(),
                            "poolTwo": ["deviceSerial3", "deviceSerial4"].toSet()
                    ]
            )
    )
}

Pooling strategies

  • PoolPerDevice - each device is its own pool, so each test will run on each device
  • SinglePool - all devices make up one pool, so each test will run only once, on an unspecified device
  • PhonesAndTablets - devices are split into pools based on type, so each test will run on one phone and one tablet
    • If the tabletShortestWidthDp property is set, devices with at least that dimension will be considered "tablets"
    • If tabletShortestWidthDp is not set, devices with tablet in their ro.build.characteristics build property will be considered "tablets"
  • Manual - create your own pools with specific devices - each test will run on one device from each pool

Compatibility with Android extension

Gordon is compatible with most testing options that can be configured in the Android extension, including size/annotation/notAnnotation arguments and disabling animations for tests. Note that you can also specify annotations using Gordon's test filtering options instead of using instrumentation runner arguments.

Example build.gradle.kts

android {
    defaultConfig {
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

        testInstrumentationRunnerArgument("size", "medium")
        testInstrumentationRunnerArgument("notAnnotation", "androidx.test.filters.FlakyTest")

        testOptions.animationsDisabled = true
    }
}

In this example, the AndroidX AndroidJUnitRunner will be used, animations will be disabled, and the only tests run will be those annotated with @MediumTest, but not @FlakyTest.

Running

Tasks

Gordon registers a Gradle task for each tested variant, stripping testBuildType (normally Debug) from the task name because it's redundant.

For example, if you have no flavors defined, the following task is registered:

  • gordon - the equivalent of connectedDebugAndroidTest

If you have a mode dimension with demo and full flavors, plus a staging build type in addition to the standard debug and release types, and you set your testBuildType to staging, the following tasks are registered:

  • gordonDemo - the equivalent of connectedDemoStagingAndroidTest
  • gordonFull - the equivalent of connectedFullStagingAndroidTest

Filtering

There is a --tests commandline option that overrides the testFilter set in the gordon extension if both are specified.

Examples

  • ./gradlew gordon
  • ./gradlew gordon --tests=ExampleTest.runThisMethod
  • ./gradlew gordon --tests=RunThisWholeTestClass
  • ./gradlew gordon --tests=ExampleTest.runThisMethod,com.example.runthispackage
  • ./gradlew gordon --tests=com.example.RunTestsWithThisAnnotation

Retries

If a retry quota is specified, Gordon will, after trying tests once, first retry any tests that were not able to run because of device issues, up to the specified quota per test case, and then retry any failing tests, up to the specified quota per test case. If multiple devices are available in a pool, a failing test will be retried on a different device from the one on which it originally failed.

Reports

Gordon generates junit reports in the build directory / test-results, and an HTML report in the build directory / reports.

Other notes

Why we named our test runner Gordon

Gordon

License

   Copyright 2019 Jack Henry & Associates, Inc.

   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.

gordon's People

Contributors

abergfeld avatar anlosar avatar banno-autobot avatar chrishoekstra avatar grzechu92 avatar joshschriever avatar rafaeltoledo avatar turpif avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gordon's Issues

Unsupported feature http://xmlpull.org/v1/doc/features.html#indent-output

Hello :)

While trying to perform some tests everything works fine until it tries to generate some reports. I'm constantly getting:

> Task :app:gordonMock FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:gordonMock'.
> unsupported feature http://xmlpull.org/v1/doc/features.html#indent-output

Good to mention that issue exists only in our project. Your sample on the same configuration works flawless. I have no idea what causes this issue. Creating my fork without this line solves it.

Do you have any ideas or tips where to look in the issue?
Is some dependency conflict possible?

Regards,
Greg

gradle imports and project resolve errors

I tried to install the plugin using readme, but I get the following errors for all flavors:

Warning:project ':app': Unable to build kotlin-kapt plugin configuration
Details: org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreationException: Could not create task ':app:gordonProd'.
Caused by: org.gradle.api.tasks.TaskInstantiationException: Could not create task of type 'GordonTestTask'.
Caused by: java.lang.NoSuchMethodError: org.gradle.api.provider.Property.zip(Lorg/gradle/api/provider/Provider;Ljava/util/function/BiFunction;)Lorg/gradle/api/provider/Provider;

"The APK Set archive does not contain the following modules" error message

It looks like since Gordon is having dependency to bundletool newer than 0.15.0 using --modules parameter causes following error message:

The APK Set archive does not contain the following modules: [module_name]

It happens when dynamic feature module is configured with install-time delivery.

    <dist:module>
        <dist:delivery>
            <dist:install-time />
        </dist:delivery>
    </dist:module>

I'm not really sure if that's a thing on Gordon or bundletool side, but it seems like not calling --modules while executing bundletool install-apks is solving the issue, which makes sense, because maybe bundetool is not treating install-time modules as "modules" by definition.
Is that --modules verification necessary? Could it be omitted or optional by some configuration parameter?

Unable to build app with Gordon 1.6.5

I'm trying to update AGP to 4.2.0 in my project and am using this issue for solve sync problem.

But i can't update gordon version to 1.6.5. Gradle is syncing, but the build failed with the following error message:

Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':as-app-base:packageDebugResources'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:187)
        at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:268)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:185)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:173)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:109)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:408)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:395)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:388)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:374)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversal

The same result with 1.7.0 version. If revert gordon to 1.6.4 build will be successful.

P.S
Android Studio 4.2, AGP 4.1.3, Gradle 7.0

Support for Android Junit5

Discussed in #123

Originally posted by gopidholakia August 28, 2023
Gordon can only support Junit4, it would be great if I get support for JUnit5 as well.

android-junit5

what I have did to have the suppoert for JUnit5 is, edit TestSuiteLoader

From
private val Annotatable.isTestMethod get() = annotationNames.any { it == "org.junit.Test" }

To
private val Annotatable.isTestMethod get() = annotationNames.any { it in setOf("org.junit.Test", "org.junit.jupiter.api.Test") }

This change is working for me, and It would be better if I get support by Gordon itself.

"Package does not have a signature matching the target" error when using signingConfigs

Since 1.4.0 version, Gordon task fails with the following message:

INSTRUMENTATION_STATUS: Error=Permission Denial: starting instrumentation ComponentInfo{com.example.gordontest.test/androidx.test.runner.AndroidJUnitRunner} from pid=10898, uid=10898 not allowed because package com.example.gordontest.test does not have a signature matching the target com.example.gordontest

After doing some tests, this stopped happening as soon as I commented out signingConfigs in gradle.

I created an example project with just one test and Espresso dependency and the same error happens when signingConfigs is added in gradle.

java.lang.NoClassDefFoundError: Could not initialize class kotlinx.html.stream.StreamKt

The test itself is running fine, but when it needs to generate the report it will fail.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:gordonAutomation'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:200)
        at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:263)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:198)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:179)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:109)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:62)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:41)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:372)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:359)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:352)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:338)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Caused by: java.lang.NoClassDefFoundError: Could not initialize class kotlinx.html.stream.StreamKt
        at com.banno.gordon.TestResultsKt.htmlReport(TestResults.kt:112)
        at com.banno.gordon.GordonTestTask$runTestsCatching$$inlined$eager$1.invokeSuspend(Effect.kt:105)
        at com.banno.gordon.GordonTestTask$runTestsCatching$$inlined$eager$1.invoke(Effect.kt)
        at arrow.continuations.generic.DelimContScope.invoke(DelimContScope.kt:91)
        at arrow.continuations.Reset.restricted(Reset.kt:37)
        at com.banno.gordon.GordonTestTask.runTestsCatching(GordonTestTask.kt:260)
        at com.banno.gordon.GordonTestTask.runTests(GordonTestTask.kt:132)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$3.run(ExecuteActionsTaskExecuter.java:555)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:540)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:523)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$300(ExecuteActionsTaskExecuter.java:108)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.executeWithPreviousOutputFiles(ExecuteActionsTaskExecuter.java:271)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:260)
        at org.gradle.internal.execution.steps.ExecuteStep.lambda$execute$1(ExecuteStep.java:34)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:34)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:26)
        at org.gradle.internal.execution.steps.CleanupOutputsStep.execute(CleanupOutputsStep.java:67)
        at org.gradle.internal.execution.steps.CleanupOutputsStep.execute(CleanupOutputsStep.java:36)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:49)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:34)
        at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:43)
        at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:73)
        at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:54)
        at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:44)
        at org.gradle.internal.execution.steps.SnapshotOutputsStep.execute(SnapshotOutputsStep.java:54)
        at org.gradle.internal.execution.steps.SnapshotOutputsStep.execute(SnapshotOutputsStep.java:38)
        at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:42)
        at org.gradle.internal.execution.steps.CacheStep.executeWithoutCache(CacheStep.java:159)
        at org.gradle.internal.execution.steps.CacheStep.execute(CacheStep.java:72)
        at org.gradle.internal.execution.steps.CacheStep.execute(CacheStep.java:43)
        at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:44)
        at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:33)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:38)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:24)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:92)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:85)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:55)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:39)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:76)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:37)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:36)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:26)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:94)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:49)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:79)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:53)
        at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:74)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:78)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:78)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:34)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:39)
        at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:40)
        at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:28)
        at org.gradle.internal.execution.impl.DefaultWorkExecutor.execute(DefaultWorkExecutor.java:33)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:187)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:179)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:109)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:62)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:41)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:372)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:359)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:352)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:338)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)

Look into exclusively using bundletool instead of jadb

See #39 - we now use bundletool to install aab files. Bundletool has some wrappers around ddmlib stuff that are nicer to work with than ddmlib, and might even be nicer than jadb in some ways. So, maybe it would be worth just using that stuff for installing the instrumentation apk and checking devices characteristics, etc. Then we could get rid of the jadb library.

issue running with tag annotation

using the example ./gradlew gordon --tests=com.example.RunTestsWithThisAnnotation
I try use the Large annotation on android /gradlew gordonDemo --tests=androidx.test.filters.LargeTest
also I found that run a simple test on 1.4.1 don't find the add, but in the 1.3.1 works without problem in the same project

on 1.4.1

* What went wrong:
Execution failed for task ':wallet:gordonDemo'.
> Unable to determine the location of ADB. Please set the --adb flag or define ANDROID_HOME or PATH environment variable.

but on 1.3.1 test not found

Using AllDevices with a specific set of devices

Hi!
I haven't seen a way to specify a list of devices when using any of the pooling strategies besides SpecificDevices. Is there a way to do this for AllDevices too?
My use case is that on my CI machine I run eight emulators and 4 Jenkins build processors. This means each build will be able to reserve two devices per run. If I am reading the source correct, AllDevices would just try to run my tests on all eight emulators. In Spoon I was able to just pass in a set of devices to use: devices = [System.getenv("EMU_ONE"), System.getenv("EMU_TWO")].

Failed to Sync project with Gordon v1.0.7 and AGP v3.6.0

Receiving the following error when syncing in Android Studio v3.6.0

  • Android Studio : com.android.tools.build:gradle:3.6.0
  • Gradle v6.2
  • Gordon v1.0.7
ERROR: Unable to find method 'com.android.build.gradle.tasks.PackageAndroidArtifact.getOutputDirectory()Ljava/io/File;'.
Possible causes for this unexpected error include:
Gradle's dependency cache may be corrupt (this sometimes occurs after a network connection timeout.)
Re-download dependencies and sync project (requires network)

The state of a Gradle build process (daemon) may be corrupt. Stopping all Gradle daemons may solve this problem.
Stop Gradle build processes (requires restart)

Your project may be using a third-party plugin which is not compatible with the other plugins in the project or the version of Gradle requested by the project.

In the case of corrupt Gradle processes, you can also try closing the IDE and then killing all Java processes.
Could not create task ':app:gordonDebugUAT'.
> com.android.build.gradle.tasks.PackageAndroidArtifact.getOutputDirectory()Ljava/io/File;

Commenting out gordon fixes the issue.

Gordon is not running in multi module project

Does Gordon support multi module projects? I'm getting this error:

<ij_msg_gr>Gradle import errors<ij_msg_gr><ij_nav>/Users/felipeerazo/AndroidStudioProjects/android-evercheck-2.0/app/build.gradle<ij_nav><i><b>project ':app': Unable to build Kotlin project configuration</b><eol>Details: org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreationException: Could not create task ':app:gordonDevelopment'.<eol>Caused by: org.gradle.api.tasks.TaskInstantiationException: Could not create task of type 'GordonTestTask'.<eol>Caused by: java.lang.NoSuchMethodError: org.gradle.api.provider.Property.zip(Lorg/gradle/api/provider/Provider;Ljava/util/function/BiFunction;)Lorg/gradle/api/provider/Provider;</i>

Pass environment variables

How can I pass environment parameters to all tests run through Gordon?

I mean the ones are usually pass in Android as testInstrumentationRunnerArguments. Parameters like clearPackageData: 'true' or custom ones like foo: bar, so in test i can get them using: InstrumentationRegistry.getArguments().getString("foo").
Will it use project-based testInstrumentationRunnerArguments or it will override them?

Support for Dynamic feature module

Hi,

I tried to use Gordon in my Dynamic Feature module but I got exception that Gordon is available only for application and android library. Do you have some plans to support Dynamic feature?

Fails to sync using Gradle 4.2.0

After upgrading to gradle 4.2.0, Gordon prevents gradle sync with the following error message:

java.lang.NoSuchMethodError: 'void com.android.build.api.dsl.CommonExtension.onVariants(kotlin.jvm.functions.Function1)'
	at com.banno.gordon.GordonPlugin.apply(GordonPlugin.kt:41)
	at com.banno.gordon.GordonPlugin.apply(GordonPlugin.kt:30) <162 internal calls>

This is reproducible from a freshly created android project. Reverting to 4.1.3 or removing 'com.banno.gordon' from applied plugins allows gradle to sync and Android Studio to build.

Gordon does not pick up all available devices

Discussed in #116

In my setup all devices are in one pool, e.g. with PoolingStrategy.SinglePool or PoolingStrategy.Manual (all devices manually added to a single pool) - tried both options to see if it makes any difference.

The scenarios I described above is that there are always devices (marked as NOT PICKED above) which are completely ignored during the whole test run. I have around 600 tests. They run (one test per device) only on the devices marked above as picked.

My expectation would be that all devices in the pool will be at some point used during the whole run. Running each test once on any of those devices.

More clarification: before the test starts I see in the log the installation of the app APK and test APK, already there the ignored devices are consistently not shown. No timeout message, etc. I also increased the installation timeout but no change.

... building part of the log ...
00:21:55.440  > Task :app:gordon
00:22:42.170  real-device-2: installing app.package.name
00:22:42.170  real-device-3: installing app.package.name
00:22:42.170  host-a:7555: installing app.package.name
00:22:42.170  host-b:8555: installing app.package.name
00:22:42.170  
00:22:42.170  The APKs have been extracted in the directory: /tmp/830506879766754033
00:22:42.170  The APKs have been extracted in the directory: /tmp/15378697174757927620
00:22:42.171  The APKs have been extracted in the directory: /tmp/14582370386234563643
00:22:42.171  The APKs have been extracted in the directory: /tmp/2033343059800847795
00:22:52.158  
00:22:52.158  > Task :app:gordon
00:22:52.158  host-b:8555: installing app.package.name.test
00:22:54.078  host-a:7555: installing app.package.name.test
00:22:57.383  host-b:8557: installing app.package.name
00:22:57.383  
00:22:57.383  The APKs have been extracted in the directory: /tmp/3186994027525455358
00:23:09.634  
00:23:09.634  > Task :app:gordon
00:23:09.634  host-b:8557: installing app.package.name.test
00:24:46.085  real-device-2: installing app.package.name.test
00:24:46.085  real-device-3: installing app.package.name.test
... tests start ....

In the log the devices: real-device-1, host-b:8559 do not show nor are those devices used.

As I said, if I turn off/disconnect the ignored devices another 2 devices will be not picked at all. In the example shown in the log if I disconnect real-device-1, real-device-2 will not be picked. If I turn off host-b:8559, host-b:8557 will not be picked by the test at all.

It seams strange. But it somehow seems as if the devices get clustered by type (real/emulator) and host where they run. The behavior described above seems to then apply for each cluster which has more than 1 device:

If I have: real-device-1, emulator-1, host-a:7555, host-b:8555 all devices get picked.
If I have: real-device-1, real-device-2, emulator-1, host-a:7555, host-b:8555 => real-device-1 does not get picked.
If I have: real-device-1, real-device-2, real-device-3, emulator-1, host-a:7555, host-b:8555 => real-device-1 does not get picked.
If I have: real-device-1, emulator-1, host-a:7555, host-b:8555, host-b:8557 => host-b:8557 does not get picked.
If I have: real-device-1, emulator-1, host-a:7555, host-b:8555, host-b:8557, host-b:8559 => host-b:8559 does not get picked.

Weird right?

How to use PoolingStrategy.SpecificDevices correctly

Hi,

We are trying to use PoolingStrategy.SpecificDevices to limit to one device as we are using HIVE CI to manage our devices.

But the following groovy code gives us the following error:

poolingStrategy.set(PoolingStrategy.SpecificDevices.INSTANCE(["xxxxxxxxxxx"]))

> No signature of method: build_ei2f2svapxfh7kbgybfe5mva7.gordon() is applicable for argument types: (build_ei2f2svapxfh7kbgybfe5mva7$_run_closure1) values: [build_ei2f2svapxfh7kbgybfe5mva7$_run_closure1@60efeda4]
  Possible solutions: run(), run(), grep(), grep(java.lang.Object)

Any ideas as to the correct syntax for this please?

Doesn't find Android extension

Describe the bug
Applying the plugin to an Android project fails with:

Android extension not found. Make sure the Android plugin is applied

I am using AGP 7.0.3 and Gradle 7.2 and 1.8.2 of Gordon (because it is closest to those versions, but it fails for 1.8.0 and 1.8.5 as well.

Run annotated tests

It would be nice if you can add the support to run test with specific annotations.

Support Annotation argument

It would be nice if there was the option to run annotated tests filtered by an argument provided in command line. Something similar to what would be achieved by using:
./gradlew connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation=com.example.CustomAnnotation

--test only supports packages, clases or methods.
Setting annotation in gradle is not flexible enough.

'androidInstrumentationRunnerOptions$gordon_plugin' is missing an input or output annotation.

Before you open an issue

Describe the bug
When using Gordon with Gradle 7.0 I'm getting this error:

Some problems were found with the configuration of task ':app:gordonDev' (type 'GordonTestTask').
  - Type 'GordonTestTask' property 'androidInstrumentationRunnerOptions$gordon_plugin' is missing an input or output annotation.

To Reproduce

  1. Apply Gordon to an Android [app|library|feature] module.
  2. Run the following gradle task: app:gordonDev
  3. See error

"Collection contains no element matching the predicate." error after applying plugin

Describe the bug
Hello, I wanted to use your runner, and faced the error. My root gradle:

dependencies {
    ...
     classpath "com.banno.gordon:gordon-plugin:1.6.2"
 }

Build was successfull. After that I tried to apply to my module:

apply plugin: 'com.android.application'
apply plugin: "com.banno.gordon"
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

And received error.
To Reproduce

  1. Apply Gordon to an Android app module.
  2. Setup was described in description blog
  3. Synchronise gradle
  4. See error
    Gradle sync failed Collection contains no element matching the predicate.
    Expected behavior
    Gradle sync successfully finished

Cannot generate reports after run tests

i'm gettings this error Caused by: org.xmlpull.v1.XmlPullParserException: No valid serializer classes found in resource /META-INF/services/org.xmlpull.v1.XmlPullParserFactory that contained 'org.xmlpull.mxp1.MXParser,org.xmlpull.mxp1_serializer.MXSerializer'

Update bundletool to 1.8.1 in gordon

Before you open an issue

  • Are you using the latest Gordon version? If not, try updating.
  • Are you using the latest Gradle version? If not, try updating. If you can't update your Gradle version, you may have to use an older Gordon version that supports the outdated Gradle version.
  • Are you using the latest Android Gradle Plugin version? If not, try updating. If you can't update your AGP version, you may have to use an older Gordon version that supports the outdated AGP version.
  • Are you sure this is a bug? If it's a feature request or idea for improvement, or if you're just not sure that your problem is caused by a Gordon bug rather than something else, consider posting in the "Ideas" or "Q&A" Discussion categories instead of opening an issue.

Describe the bug
There is an error with Android 12(Android12 emulator fails due to 'Error retrieving device density'), this error fixed at Bundletool 1.8.1, gordonMock task don't work because the version bundletool is 1.8.0

Expected behavior
Update bundletool to 1.8.1 in gordon

Additional context

Parameterized tests are counted as failure

Hello,

I've tested Gordon and it makes one of my test failing whereas it is not. I managed to see that it is because Gordon does not seems to support Parameterized tests runner.

Parameterized tests are counted as failure. In the XML report, a failure text is:

Failure 1
com.foo.BarTest:.............................................................................

Time: 6.681

OK (77 tests)

And I think that this may come from here: https://github.com/Banno/Gordon/blob/master/gordon-plugin/src/main/kotlin/com/banno/gordon/TestRunner.kt#L51
The string OK (1 test) is search, and for parameterized tests, as there is more than 1 test, they end up in the failures.
We could imagine fixing this by searching a OK \([1-9][0-9]* tests?) pattern instead.

Does ReadMe instructions for "old Gradle plugins syntax" need an update?

In the ReadMe file, the instructions for the "old Gradle plugins syntax" are:

buildscript {
    repositories {
        gradlePluginPortal()
        google()
        jcenter()
        maven { url "https://jitpack.io" }
    }

    dependencies {
        classpath "com.banno.gordon:gordon-plugin:$gordonVersion"
    }
}

But I could only make it work with a slightly different code:

buildscript {
    ext.gordonVersion = "1.0.6"
    repositories {
        ...
    }

    dependencies {
        classpath "com.github.Banno:Gordon:$gordonVersion"
    }
}

Otherwise it would give an error saying that it couldn't find Gordon in any classpath.

Am I doing something wrong or could you please check if the ReadMe file needs any changes?

Error with minSdkVersion 24

After upgrading the app to use minSdkVersion 24 (Android 7.0 (API level 24)) the plugin is not working. Every time we try to run it I get the error:

What went wrong: Execution failed for task :app:gordonProd bad magic value: 64 65 78 0a 30 33 37 00

Tests with @SdkSuppress annotation are displayed as failed

Steps:
Test method annotated with @SdkSuppress(maxSdkVersion = 19).

Actual result
In reports (both HTML and JUnit) displayed as failed.

Expected result
Maybe it should be marked as skipped.

DeprecatedScreenTest.testShouldRedirectToUpdateApplicationWhenDeprecated

Failure 1
Time: 0
OK (0 tests)

Failure 2
Time: 0.001
OK (0 tests)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.