android / android-test

An extensive framework for testing Android apps
https://android.github.io/android-test
Apache License 2.0
1.16k stars 315 forks source link

Frequent 'No tests found' when sharding tests #973

Open chrisbanes opened 3 years ago

chrisbanes commented 3 years ago

Description

When sharding tests I get frequent (and seemingly random) 'No tests found' errors which fail the build.

See this PR: https://github.com/google/accompanist/pull/463 for an example.

Steps to Reproduce

I'm not 100% sure what the repro is. Having a small number of tests in the module seems to be the trigger.

I think this is because the library is relying on hashCode being uniformly random over a small data-set, which isn't the case. If the module only has ~5 tests, and I'm sharding over 3 runs, the chance of at least 1 shard having 0 tests is quite high.

Expected Results

Shards with no tests from a module don't fail the build.

Actual Results

com.android.build.gradle.internal.testing.ConnectedDevice > No tests found.[test(AVD) - 5.1.1] FAILED 
No tests found. This usually means that your test classes are not in the form that your test runner expects (e.g. don't inherit from TestCase or lack @Test annotations).

AndroidX Test and Android OS Versions

1.3.0

Link to a public git repo demonstrating the problem:

https://github.com/google/accompanist/pull/463

chrisbanes commented 3 years ago

264 looks similar

brettchabot commented 3 years ago

AJUR's algorithm is a fork of bazel's HashBackedStrategy. https://github.com/bazelbuild/bazel/blob/master/src/java_tools/junitrunner/java/com/google/testing/junit/runner/sharding/HashBackedShardingFilter.java

However, I notice that the bazel default is a RoundRobinStrategy https://github.com/bazelbuild/bazel/blob/master/src/java_tools/junitrunner/java/com/google/testing/junit/runner/sharding/RoundRobinShardingFilter.java

I cannot remember why the hash-backed algorithm was chosen for AJUR - perhaps because it was simpler...?

Changing the sharding algorithm could be disruptive, but perhaps we could make it configurable like bazel...

TWiStErRob commented 3 years ago

I think the hash based was chosen because of its stability, or at least that's a good argument for it. Adding/removing/renaming a test doesn't affect all the shards only that one test (maybe) moves to another shard, the rest is exactly as is. While with the round robin, renaming a test would totally reassign shards. This type of stability is really good for reproducibility and debugging flakyness. +1 for custom sharding with some good built-in examples.

Apetree100122 commented 11 months ago

<?php?> package androidx.test.ui.app ;import android.app.Service;import android.content.Intent ;import android.content.res.Configuration;import android.graphics.Color; import android.graphics.PixelFormat;import android.graphics.drawable.ColorDrawable;import android.graphics.drawable.Drawable;import android.os.Build;import android.os.IBinder;import android.util.Log;import android.view.Gravity;import android.view.MotionEvent;import android.view.View;import android.view.View.OnClickListener;import android.view.WindowManager;import android.view.WindowManager.LayoutParams;import android.widget.ImageView;/* Will use WindowManager to create a chat head button on the screen./public class ChatHeadService extends Service { private static final String TAG = "ChatHeadService"; private WindowManager windowManager; private ImageView chatHeadButton; private boolean isRedColor = true; @Override public void onConfigurationChanged(Configuration newConfig) {Log.i(TAG, "Destroying and re-creating the chat head because of a configuration changed: " + newConfig); destroyChatHead(); createChatHead(); } @Override public void onCreate() { super.onCreate(); createChatHead(); } private void createChatHead() { // create a chat head button chatHeadButton = new ImageView(this);chatHeadButton.setId(R.id.chat_head_btn_id); setChatHeadColor(isRedColor);windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); final LayoutParams layoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, // width LayoutParams.WRAP_CONTENT, // height LayoutParams.TYPE_PHONE, // type LayoutParams.FLAG_NOT_FOCUSABLE, // flagsPixelFormat.TRANSLUCENT); // formatlayoutParams.gravity = Gravity.BOTTOM; // add the chat head to window manager windowManager.addView(chatHeadButton, layoutParams); // for moving the button on touch and slide chatHeadButton.setOnTouchListener(new View.OnTouchListener() { private int initialX; private int initialY; private float initialTouchY; @Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:initialX = layoutParams.x;initialY = layoutParams.initialTouchX = event.getRawX();Initial: TouchY = event.getRawY(); break;case MotionEvent.ACTION_UP: if (Math.abs(event.getRawX() - initialTouchX) == 0 &

Apetree100122 commented 11 months ago

$variable_name = value;

Loops

  1. IF Family: If, If-else, Nested-Ifs are used when you want to perform a certain set of operations based on conditional expressions.

If if(conditional-expression){
//code
} If-else if(conditional-expression){
//code if condition is true
} else {
//code if condition is false
} Nested-If-else if(condition-expression1) {
//code if above condition is true
} elseif(condition-expression2){
//code if above condition is true
}
elseif(condition-expression3) {
//code if above condition is true
}
...
else {
//code if all the conditions are false
}

  1. Switch: Switch is used to execute one set of statement from multiple conditions.

switch(conditional-expression) {
case value1:
// code if the above value is matched
break; // optional
case value2:
// code if the above value is matched
break; // optional
...

default:
// code to be executed when all the above cases are not matched;
}

  1. For: For loop is used to iterate a set of statements based on a condition.

for(Initialization; Condition; Increment/decrement){
// code
} For-each: // you can use any of the below syntax foreach ($array as $element-value) {
//code
}

foreach ($array as $key => $element-value) {
//code }

  1. While: While is also used to iterate a set of statements based on a condition. Usually while is preferred when number of iterations are not known in advance.

while(condition) {
// code }

  1. Do-While: Do-while is also used to iterate a set of statements based on a condition. It is mostly used when you need to execute the statements atleast once.

do { // code } while (condition); Functions Function is a sub-routine which contains set of statements. Usually functions are written when multiple calls are required to same set of statements which increases re-usuability and modularity.

How to define a Function function function_name(parameters) {
//code } How to call a Function function_name (parameters)