package com.jetbrains.env.python.testing;

import com.google.common.collect.ImmutableList;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.RunManager;
import com.intellij.execution.configurations.RuntimeConfigurationWarning;
import com.intellij.execution.testframework.AbstractTestProxy;
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.testFramework.fixtures.CodeInsightTestFixture;
import com.intellij.util.PathUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.env.EnvTestTagsRequired;
import com.jetbrains.env.PyEnvTestCase;
import com.jetbrains.env.PyExecutionFixtureTestTask;
import com.jetbrains.env.PyProcessWithConsoleTestTask;
import com.jetbrains.env.python.testing.CreateConfigurationTestTask.PyConfigurationValidationTask;
import com.jetbrains.env.ut.PyTestTestProcessRunner;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyPsiPackageUtil;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.packaging.PyRequirementParser;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.run.targetBasedConfiguration.PyRunTargetVariant;
import com.jetbrains.python.sdk.InvalidSdkException;
import com.jetbrains.python.testing.*;
import com.jetbrains.python.tools.sdkTools.SdkCreationType;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;

import java.io.IOException;
import java.io.StringReader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;

import static com.intellij.execution.testframework.sm.runner.ui.MockPrinter.fillPrinter;
import static com.jetbrains.env.ut.PyScriptTestProcessRunner.TEST_TARGET_PREFIX;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

/**
 * User : catherine
 */
@EnvTestTagsRequired(tags = "pytest")
public final class PythonPyTestingTest extends PyEnvTestCase {


  // Ensures setup/teardown does not break anything
  @Test
  public void testSetupTearDown() {
    runPythonTest(new SetupTearDownTestTask<PyTestTestProcessRunner>() {
      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner("test_test.py", 1);
      }
    });
  }

  /**
   * assertEquals from TestCase launched by pytest
   */
  @Test
  public void testDiffUnit() {
    runPythonTest(new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/diff_unit", SdkCreationType.EMPTY_SDK) {

      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner("test_diff.py", 1);
      }

      @Override
      protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                      @NotNull final String stdout,
                                      @NotNull final String stderr,
                                      @NotNull final String all,
                                      int exitCode) {

        Iterable<Matcher<? super String>> expectedConsoleText =
          ContainerUtil.map(ImmutableList.of("Expected :'A'", "Actual   :'B'", "<Click to see difference>"), Matchers::containsString);
        var consoleText = runner.getAllConsoleText();
        assertThat("No diff", consoleText, allOf(expectedConsoleText));
        assertThat("Wrong line", consoleText, containsString("test_diff.py:7: AssertionError"));
      }
    });
  }


  @Test
  public void testExit() {
    runPythonTest(new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/exit", SdkCreationType.EMPTY_SDK) {

      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        var runner = new PyTestTestProcessRunner("test_test.py", 0);
        runner.setSkipExitCodeAssertion(true);
        return runner;
      }

      @Override
      protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                      @NotNull final String stdout,
                                      @NotNull final String stderr,
                                      @NotNull final String all,
                                      int exitCode) {

        assertEquals("Test must be skipped", """
          Test tree:
          [root](~)
          .test_test(~)
          ..test_(~)
          """, runner.getFormattedTestTree());
      }
    });
  }

  @Test
  public void testDiff() {
    runPythonTest(new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/diff", SdkCreationType.EMPTY_SDK) {

      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner("test_diff.py", 1);
      }

      @Override
      protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                      @NotNull final String stdout,
                                      @NotNull final String stderr,
                                      @NotNull final String all,
                                      int exitCode) {

        final String expectedConsoleText = """
          Expected :'expected'
          Actual   :'actual'
          <Click to see difference>""";
        assertThat("No diff", runner.getAllConsoleText(), containsString(expectedConsoleText));
      }
    });
  }

  /**
   * Provides existing .xml and checks that configuration is able to parse it
   */
  @Test
  public void testDeserialization() {
    runPythonTest(new PyExecutionFixtureTestTask("testRunner/env/pytest/config/") {
      @Override
      public void runTestOn(@NotNull String sdkHome, @Nullable Sdk existingSdk) throws Exception {
        final PyTestConfiguration configuration = configureByFile("MyConfig.xml");
        assertEquals("Wrong target type", new ConfigurationTarget("foo.py", PyRunTargetVariant.PATH), configuration.getTarget());
        assertEquals("Wrong keywords", "keywords", configuration.getKeywords());
        assertEquals("Wrong arguments", "--additional-args", configuration.getAdditionalArguments());


        final PyTestConfiguration configurationWithCustom = configureByFile("MyConfigWithCustom.xml");
        assertEquals("Wrong target type", new ConfigurationTarget("spam", PyRunTargetVariant.CUSTOM), configurationWithCustom.getTarget());
        assertEquals("Wrong keywords", "keywords", configurationWithCustom.getKeywords());
        assertEquals("Wrong arguments", "--additional-args", configurationWithCustom.getAdditionalArguments());
      }

      @NotNull
      private PyTestConfiguration configureByFile(@NotNull final String path) throws IOException, JDOMException {
        final VirtualFile file = myFixture.getTempDirFixture().getFile(path);
        assert file != null : "No config found";

        final SAXBuilder builder = new SAXBuilder();
        final CharBuffer data = Charset.defaultCharset().decode(ByteBuffer.wrap(file.contentsToByteArray()));
        final Element element = builder.build(new StringReader(data.toString())).getRootElement();

        final PyTestConfiguration configuration =
          new PyTestConfiguration(myFixture.getProject(), new PyTestFactory(PythonTestConfigurationType.getInstance()));
        configuration.readExternal(element);
        return configuration;
      }
    });
  }

  /**
   * Ensures that python target pointing to module works correctly
   */
  @Test
  public void testRunModuleAsFile() {
    runPythonTest(new RunModuleAsFileTask<PyTestTestProcessRunner>() {
      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner(TARGET, 0);
      }
    });
  }


  @Test
  public void testRerunSubfolder() {
    runPythonTest(new RerunSubfolderTask<PyTestTestProcessRunner>(2) {
      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner(".", 1);
      }
    });
  }

  @EnvTestTagsRequired(tags = "-messages") //messages registered 2 times when launched with testdir plugin, should be fixed separately
  @Test
  public void testTestDirFixture() {
    runPythonTest(new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/testdir", SdkCreationType.EMPTY_SDK) {

      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner("test_foo.py", 0);
      }

      @Override
      protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                      @NotNull final String stdout,
                                      @NotNull final String stderr,
                                      @NotNull final String all,
                                      int exitCode) {

        assertEquals(stderr, 1, runner.getAllTestsCount());
        assertEquals(stderr, 1, runner.getPassedTestsCount());
      }
    });
  }

  /**
   * Test name must be reported as meta info to be used as parameter for for parametrized tests
   */
  @Test
  public void testMetaInfoForMethod() {
    runPythonTest(new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/", SdkCreationType.EMPTY_SDK) {

      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner("test_with_method.py", 0);
      }

      @Override
      protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                      @NotNull final String stdout,
                                      @NotNull final String stderr,
                                      @NotNull final String all,
                                      int exitCode) {

        final String testName = "test_method";
        final AbstractTestProxy method = runner.findTestByName(testName);
        assert method != null : "Method not reported";
        assertEquals("Meta info must be test name", testName, method.getMetainfo());
      }
    });
  }


  @Test
  public void testParametrized() {
    runPythonTest(
      new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/parametrized", SdkCreationType.EMPTY_SDK) {

        @NotNull
        @Override
        protected PyTestTestProcessRunner createProcessRunner() {
          return new PyTestTestProcessRunner("test_pytest_parametrized.py", 1);
        }

        @Override
        protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                        @NotNull final String stdout,
                                        @NotNull final String stderr,
                                        @NotNull final String all,
                                        int exitCode) {
          assertEquals("Parametrized test produced bad tree", "Test tree:\n" +
                                                              "[root](-)\n" +
                                                              ".test_pytest_parametrized(-)\n" +
                                                              "..test_eval(-)\n" +
                                                              "...(three_plus file-8)(-)\n" +
                                                              ((runner.getCurrentRerunStep() == 0) ? "...((2)+(4)-6)(+)\n" : "") +
                                                              "...( six times nine_-42)(-)\n", runner.getFormattedTestTree());
        }
      });
  }

  /**
   * Ensure that testName[param] is only launched for parametrized test if param provided
   */
  @Test
  public void testParametrizedRunByParameter() {
    runPythonTest(
      new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/parametrized", SdkCreationType.EMPTY_SDK) {

        @NotNull
        @Override
        protected PyTestTestProcessRunner createProcessRunner() {
          return new PyTestTestProcessRunner("test_pytest_parametrized.py", 1) {
            @Override
            protected void configurationCreatedAndWillLaunch(@NotNull PyTestConfiguration configuration) throws IOException {
              super.configurationCreatedAndWillLaunch(configuration);
              configuration.getTarget().setTarget("test_pytest_parametrized.test_eval");
              configuration.getTarget().setTargetType(PyRunTargetVariant.PYTHON);
              if (isPyTestVersionAtLeast(configuration, "7.0.0")) {
                // Since 7.0.0 we need to provide argument for debug
                configuration.setAdditionalArguments("--debug log.txt");
              }
              else {
                configuration.setAdditionalArguments("--debug");
              }
              configuration.setMetaInfo("test_eval[three.plus file-8]");
            }
          };
        }

        @Override
        protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                        @NotNull final String stdout,
                                        @NotNull final String stderr,
                                        @NotNull final String all,
                                        int exitCode) {
          assertEquals("Only one test should be launched", """
            Test tree:
            [root](-)
            .test_pytest_parametrized(-)
            ..test_eval(-)
            ...(three_plus file-8)(-)
            """, runner.getFormattedTestTree());
        }
      });
  }

  private static boolean isPyTestVersionAtLeast(@NotNull PyTestConfiguration configuration, @NotNull String version) {
    try {
      var packages = PyPackageManager.getInstance(configuration.getSdk()).refreshAndGetPackages(false);
      return PyPsiPackageUtil.findPackage(packages, "pytest").matches(PyRequirementParser.fromLine("pytest>=" + version));
    }
    catch (ExecutionException e) {
      throw new RuntimeException(e);
    }
  }


  /**
   * See https://github.com/JetBrains/teamcity-messages/issues/131
   */
  @Test
  public void testTestNameBeforeTestStarted() {
    runPythonTest(new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/testNameBeforeTestStarted",
                                                                            SdkCreationType.EMPTY_SDK) {

      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner("test_test.py", 0);
      }

      @Override
      protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                      @NotNull final String stdout,
                                      @NotNull final String stderr,
                                      @NotNull final String all,
                                      int exitCode) {
        assertEquals("Test name before message broke output", """
          Test tree:
          [root](+)
          .test_test(+)
          ..SampleTest1(+)
          ...test_sample_1(+)
          ...test_sample_2(+)
          ...test_sample_3(+)
          ...test_sample_4(+)
          ..SampleTest2(+)
          ...test_sample_5(+)
          ...test_sample_6(+)
          ...test_sample_7(+)
          ...test_sample_8(+)
          """, runner.getFormattedTestTree());
      }
    });
  }


  /**
   * Each node must contain "raise Exception" only once and no "Assertion Failed" message
   */
  @EnvTestTagsRequired(tags = "python3")
  @Test
  public void testAssertionFailedNotDuplicated() {
    runPythonTest(
      new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/fail_tree/", SdkCreationType.EMPTY_SDK) {

        @NotNull
        @Override
        protected PyTestTestProcessRunner createProcessRunner() {
          return new PyTestTestProcessRunner(TEST_TARGET_PREFIX + "test_test", 0);
        }

        @Override
        protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                        @NotNull final String stdout,
                                        @NotNull final String stderr,
                                        @NotNull final String all,
                                        final int exitCode) {
          var assertionFailedMessage = TestStateInfo.Magnitude.FAILED_INDEX.getTitle();
          var errorMessage = "raise Exception";

          var tests = new AbstractTestProxy[]{runner.getTestProxy(), runner.findTestByName("TestTest"), runner.findTestByName("test_test")};
          for (var testText : ContainerUtil.map(tests, o -> fillPrinter(o).getAllOut())) {
            assertThat("Redundant error message", testText, not(containsString(assertionFailedMessage)));
            assertThat("No required error message", testText, containsString(errorMessage));
            assertEquals("Wrong number of real error messages", 2, testText.split(errorMessage).length);
          }
        }
      });
  }

  @Test
  public void testKeywords() {
    runPythonTest(new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/keywords", SdkCreationType.EMPTY_SDK) {

      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner("test_test.py", 0) {
          @Override
          protected void configurationCreatedAndWillLaunch(@NotNull final PyTestConfiguration configuration) throws IOException {
            super.configurationCreatedAndWillLaunch(configuration);
            configuration.setKeywords("not spam");
          }
        };
      }

      @Override
      protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                      @NotNull final String stdout,
                                      @NotNull final String stderr,
                                      @NotNull final String all,
                                      final int exitCode) {
        assertEquals("""
                       Test tree:
                       [root](+)
                       .test_test(+)
                       ..test_eggs(+)
                       """, runner.getFormattedTestTree());
        assertEquals(0, exitCode);
      }
    });
  }

  @Test
  public void testKeywordsIgnoredInCustom() {
    runTestWithJunkKeywords(true);
  }

  @Test
  public void testTestEmptySuite() {
    runTestWithJunkKeywords(false);
  }

  private void runTestWithJunkKeywords(boolean useCustomMode) {
    runPythonTest(new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/testNameBeforeTestStarted",
                                                                            SdkCreationType.EMPTY_SDK) {

      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner("test_test.py", 0) {
          @Override
          protected void configurationCreatedAndWillLaunch(@NotNull final PyTestConfiguration configuration) throws IOException {
            super.configurationCreatedAndWillLaunch(configuration);
            configuration.setKeywords("asdasdasd");
            if (useCustomMode) {
              configuration.getTarget().setTargetType(PyRunTargetVariant.CUSTOM);
            }
          }
        };
      }

      @Override
      protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                      @NotNull final String stdout,
                                      @NotNull final String stderr,
                                      @NotNull final String all,
                                      final int exitCode) {
        if (useCustomMode) {
          assertEquals("Wrong test launched", """
            Test tree:
            [root](+)
            .test_test(+)
            ..SampleTest1(+)
            ...test_sample_1(+)
            ...test_sample_2(+)
            ...test_sample_3(+)
            ...test_sample_4(+)
            ..SampleTest2(+)
            ...test_sample_5(+)
            ...test_sample_6(+)
            ...test_sample_7(+)
            ...test_sample_8(+)
            """, runner.getFormattedTestTree());
          runner.getFormattedTestTree();
        }
        else {
          assertEquals("Wrong message for empty suite", PyBundle.message("runcfg.tests.empty_suite"),
                       runner.getTestProxy().getPresentation());
          assertEquals("Wrong empty suite tree", """
            Test tree:
            [root](-)
            """, runner.getFormattedTestTree());
        }
      }
    });
  }


  @Test
  @EnvTestTagsRequired(tags = "xdist")
  public void testParallelWithSetup() {
    runPythonTest(new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/parallel", SdkCreationType.EMPTY_SDK) {

      @NotNull
      @Override
      protected PyTestTestProcessRunner createProcessRunner() {
        return new PyTestTestProcessRunner("test_parallel.py", 0) {
          @Override
          protected void configurationCreatedAndWillLaunch(@NotNull PyTestConfiguration configuration) throws IOException {
            super.configurationCreatedAndWillLaunch(configuration);
            configuration.setAdditionalArguments("-n 4");
          }
        };
      }

      @Override
      protected void checkTestResults(@NotNull PyTestTestProcessRunner runner,
                                      @NotNull String stdout,
                                      @NotNull String stderr,
                                      @NotNull String all,
                                      int exitCode) {
        assertThat("xdist not launched?", all, containsString("gw0"));
        assertEquals("""
                       Test tree:
                       [root](+)
                       .test_parallel(+)
                       ..ExampleTestCase(+)
                       ...test_example(+)
                       """,
                     runner.getFormattedTestTree());
      }
    });
  }

  // Ensure test survives patched strftime
  @Test
  public void testMonkeyPatch() {
    runPythonTest(
      new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/monkeyPatch", SdkCreationType.EMPTY_SDK) {

        @NotNull
        @Override
        protected PyTestTestProcessRunner createProcessRunner() {
          return new PyTestTestProcessRunner("test_test.py", 0);
        }

        @Override
        protected void checkTestResults(@NotNull PyTestTestProcessRunner runner,
                                        @NotNull String stdout,
                                        @NotNull String stderr,
                                        @NotNull String all,
                                        int exitCode) {
          assertEquals("Monkeypatch broke the test: " + stderr, 1, runner.getPassedTestsCount());
        }
      });
  }

  // Ensure slow test is not run when -m "not slow" is provided
  @Test
  public void testMarkerWithSpaces() {
    runPythonTest(
      new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/test_with_markers", SdkCreationType.EMPTY_SDK) {

        @NotNull
        @Override
        protected PyTestTestProcessRunner createProcessRunner() {
          return new PyTestTestProcessRunner("test_with_markers.py", 0) {
            @Override
            protected void configurationCreatedAndWillLaunch(@NotNull PyTestConfiguration configuration) throws IOException {
              super.configurationCreatedAndWillLaunch(configuration);
              configuration.setAdditionalArguments("-m 'not slow'");
            }
          };
        }


        @Override
        protected void checkTestResults(@NotNull PyTestTestProcessRunner runner,
                                        @NotNull String stdout,
                                        @NotNull String stderr,
                                        @NotNull String all,
                                        int exitCode) {
          assertEquals("Marker support broken", """
                         Test tree:
                         [root](+)
                         .test_with_markers(+)
                         ..test_fast(+)
                         """,
                       runner.getFormattedTestTree());
        }
      });
  }


  /**
   * New configuration should have closest src set as its working dir
   */
  @Test
  public void testClosestSrcIsWorkDirOnNewConfig() {
    runPythonTest(new CreateConfigurationTestTask<>(getFrameworkId(), PyTestConfiguration.class) {
      @NotNull
      @Override
      protected List<PsiElement> getPsiElementsToRightClickOn() {
        configureSrcFolder(myFixture);

        myFixture.configureByFile("test_with_src/foo/src/test_test.py");
        final PyFunction test = myFixture.findElementByText("test_test", PyFunction.class);
        assert test != null;
        return Collections.singletonList(test);
      }

      @Override
      protected void checkConfiguration(@NotNull PyTestConfiguration configuration, @NotNull PsiElement elementToRightClickOn) {
        super.checkConfiguration(configuration, elementToRightClickOn);
        assertThat("Wrong configuration directory set on new config", configuration.getWorkingDirectory(), Matchers.endsWith("src"));
      }
    });
  }

  /**
   * New configuration should have the module root as the working directory in the absence of an explicit test root
   */
  @Test
  public void testWorkDirIsModuleRoot() {
    runPythonTest(new CreateConfigurationTestTask<>(getFrameworkId(), PyTestConfiguration.class) {
      @NotNull
      @Override
      protected List<PsiElement> getPsiElementsToRightClickOn() {
        myFixture.configureByFile("conftest_in_parent_dir/tests/subdir/test_test.py");
        final PyFunction test = myFixture.findElementByText("test_myfixture", PyFunction.class);
        assert test != null;
        return Collections.singletonList(test);
      }

      @Override
      protected void checkConfiguration(@NotNull PyTestConfiguration configuration, @NotNull PsiElement elementToRightClickOn) {
        super.checkConfiguration(configuration, elementToRightClickOn);
        final String moduleRoot = myFixture.getTempDirPath();
        MatcherAssert.assertThat("Wrong configuration directory set on new config",
                                 configuration.getWorkingDirectory(),
                                 Matchers.equalTo(moduleRoot));
      }
    });
  }

  /**
   * In case when workdir is not set we should use closest src
   */
  @Test
  public void testClosestSrcIsWorkDirDynamically() {
    runPythonTest(
      new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/createConfigurationTest/", SdkCreationType.EMPTY_SDK) {
        @NotNull
        @Override
        protected PyTestTestProcessRunner createProcessRunner() {
          return new PyTestTestProcessRunner(TEST_TARGET_PREFIX + "test_test.test_test", 0) {
            @Override
            protected void configurationCreatedAndWillLaunch(@NotNull PyTestConfiguration configuration) throws IOException {
              super.configurationCreatedAndWillLaunch(configuration);
              // Reset dir to check it is calculated correctly
              configuration.setWorkingDirectory(null);
              configureSrcFolder(myFixture);
              assertThat("Wrong configuration directory calculated", configuration.getWorkingDirectorySafe(), Matchers.endsWith("src"));
            }
          };
        }


        @Override
        protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
                                        @NotNull final String stdout,
                                        @NotNull final String stderr,
                                        @NotNull final String all,
                                        int exitCode) {
          assertEquals("Failed to run test" + stderr, 1, runner.getPassedTestsCount());
        }
      });
  }

  //TODO: DOC
  private static void configureSrcFolder(@NotNull final CodeInsightTestFixture fixture) {
    final ModuleRootManager manager = ModuleRootManager.getInstance(fixture.getModule());
    final ModifiableRootModel model = manager.getModifiableModel();
    final VirtualFile srcToMark = fixture.getTempDirFixture().getFile("test_with_src/foo/src");
    assert srcToMark != null;
    model.addContentEntry(srcToMark);
    model.commit();
  }

  @Test
  public void testConfigurationProducer() {
    runPythonTest(new CreateConfigurationByFileTask<>(getFrameworkId(), PyTestConfiguration.class));
  }

  @Test
  @EnvTestTagsRequired(tags = "python3")
  public void testResolveQName() {
    runPythonTest(new CreateConfigurationTestTask.PyConfigurationCreationTask() {
      @NotNull
      @Override
      protected PyAbstractTestFactory<PyTestConfiguration> createFactory() {
        return new PyTestFactory(PythonTestConfigurationType.getInstance());
      }

      @Override
      public void runTestOn(@NotNull String sdkHome, @Nullable Sdk existingSdk) {
        super.runTestOn(sdkHome, existingSdk);
        myFixture.copyDirectoryToProject("testRunner/env/createConfigurationTest/configurationByContext/foo", "");
        final PyAbstractTestConfiguration configuration = getConfiguration();
        configuration.getTarget().setTargetType(PyRunTargetVariant.PYTHON);
        configuration.getTarget().setTarget("test_foo");
        configuration.setWorkingDirectory(myFixture.getTempDirPath());

        ReadAction.run(() -> assertThat("Failed to resolve qname", configuration.getTarget().asPsiElement(configuration),
                                        Matchers.instanceOf(PyFile.class)));
      }
    });
  }

  @Test
  public void testMultipleCases() {
    runPythonTest(new CreateConfigurationMultipleCasesTask<>(getFrameworkId(), PyTestConfiguration.class));
  }

  @Test
  public void testFileStartsWithDirName() {
    runPythonTest(new CreateConfigurationTestTask<>(getFrameworkId(), PyTestConfiguration.class) {
      private static final String FOLDER_NAME = "test";

      @Override
      public void runTestOn(@NotNull String sdkHome, @Nullable Sdk existingSdk) throws InvalidSdkException {
        markFolderAsTestRoot(FOLDER_NAME);
        super.runTestOn(sdkHome, existingSdk);
      }

      @Override
      protected void checkConfiguration(@NotNull final PyTestConfiguration configuration, @NotNull final PsiElement elementToRightClickOn) {
        assertEquals("Wrong target created", "test_test.TestFoo", configuration.getTarget().getTarget());
      }

      @NotNull
      @Override
      protected List<PsiElement> getPsiElementsToRightClickOn() {
        var file = (PyFile)myFixture.configureByFile(FOLDER_NAME + "/test_test.py");
        var clazz = file.findTopLevelClass("TestFoo");
        assert clazz != null : "No test found";
        return Collections.singletonList(clazz);
      }
    });
  }

  /**
   * PY-49932
   */
  @Test
  public void testFilesSameName() {
    runPythonTest(new CreateConfigurationTestTask<>(getFrameworkId(), PyTestConfiguration.class) {
      private static final String FOLDER_NAME = "same_names";

      @Override
      public void runTestOn(@NotNull String sdkHome, @Nullable Sdk existingSdk) throws InvalidSdkException {
        markFolderAsTestRoot(FOLDER_NAME);
        super.runTestOn(sdkHome, existingSdk);
      }

      @Override
      protected void checkConfiguration(@NotNull final PyTestConfiguration configuration, @NotNull final PsiElement elementToRightClickOn) {
        assertEquals("Wrong target fore newly created element", "true.test_something.test_test", configuration.getTarget().getTarget());
      }

      @NotNull
      @Override
      protected List<PsiElement> getPsiElementsToRightClickOn() {
        var file = (PyFile)myFixture.configureByFile(FOLDER_NAME + "/true/test_something.py");
        PyFunction test = file.findTopLevelFunction("test_test");
