Skip to content

Commit

Permalink
Use separate formatter for Elixir >= 1.4.0
Browse files Browse the repository at this point in the history
Use a seperate formatter, one GenEvent-based and one GenServer-based,
depending on whether the SDK is >= 1.4.0 as that's when GenEvent was
deprecated and GenServer was preferred.
  • Loading branch information
KronicDeth committed Jul 16, 2017
1 parent a5548e1 commit b0e8f9a
Show file tree
Hide file tree
Showing 8 changed files with 505 additions and 401 deletions.
22 changes: 22 additions & 0 deletions resources/exunit/1.1.0/team_city_ex_unit_formatter.ex
@@ -0,0 +1,22 @@
# Originally based on https://github.com/lixhq/teamcity-exunit-formatter, but it did not work for parallel tests: IDEA
# does not honor flowId, so needed to use the nodeId/parentNodeIde system
#
# nodeId and parentNodeId system is documented in
# https://intellij-support.jetbrains.com/hc/en-us/community/posts/115000389550/comments/115000330464
defmodule TeamCityExUnitFormatter do
@moduledoc false

use GenEvent

# Functions

## GenEvent Callbacks

def handle_event(event, state) do
updated_state = TeamCityExUnitFormatting.put_event(state, event)

{:ok, updated_state}
end

def init(opts), do: {:ok, TeamCityExUnitFormatting.new(opts)}
end
17 changes: 17 additions & 0 deletions resources/exunit/1.4.0/team_city_ex_unit_formatter.ex
@@ -0,0 +1,17 @@
defmodule TeamCityExUnitFormatter do
@moduledoc false

use GenServer

# Functions

## GenServer Callbacks

def handle_cast(event, state) do
updated_state = TeamCityExUnitFormatting.put_event(state, event)

{:noreply, updated_state}
end

def init(opts), do: {:ok, TeamCityExUnitFormatting.new(opts)}
end
Expand Up @@ -3,41 +3,71 @@
#
# nodeId and parentNodeId system is documented in
# https://intellij-support.jetbrains.com/hc/en-us/community/posts/115000389550/comments/115000330464
defmodule TeamCityExUnitFormatter do
@moduledoc false

use GenEvent
defmodule TeamCityExUnitFormatting do
# Constants

@root_parent_node_id 0

# Struct

defstruct failures_counter: 0,
invalids_counter: 0,
seed: nil,
skipped_counter: 0,
tests_counter: 0,
trace: false,
width: 80

# Functions

@doc false
def formatter(_color, msg), do: msg

## GenEvent Callbacks
def new(opts) do
{
:ok,
%__MODULE__{
seed: opts[:seed],
trace: opts[:trace]
}
}
end

def handle_event({:case_finished, test_case = %ExUnit.TestCase{}}, config) do
def put_event(state, {:case_finished, test_case = %ExUnit.TestCase{}}) do
put_formatted :test_suite_finished, attributes(test_case)

{:ok, config}
state
end

def handle_event({:case_started, test_case = %ExUnit.TestCase{}}, config) do
def put_event(state, {:case_started, test_case = %ExUnit.TestCase{}}) do
put_formatted :test_suite_started, attributes(test_case)

{:ok, config}
state
end

def handle_event(
{:test_finished, test = %ExUnit.Test{state: {:failed, {_, reason, _} = failed},}, time: time},
config
def put_event(
state = %__MODULE{
failures_counter: failures_counter,
tests_counter: tests_counter,
width: width
},
{
:test_finished,
test = %ExUnit.Test{
state: failed = {
:failed,
{_, reason, _}
},
time: time
}
}
) do
updated_failures_counter = failures_counter + 1
formatted = ExUnit.Formatter.format_test_failure(
test,
failed,
config.failures_counter + 1,
config.width,
updated_failures_counter,
width,
&formatter/2
)
attributes = attributes(test)
Expand All @@ -54,23 +84,27 @@ defmodule TeamCityExUnitFormatter do
duration: div(time, 1000)
)

{
:ok,
%{
config |
tests_counter: config.tests_counter + 1,
failures_counter: config.failures_counter + 1
}
%{
state |
tests_counter: tests_counter + 1,
failures_counter: updated_failures_counter
}
end

def handle_event({:test_finished, test = %ExUnit.Test{state: {:failed, failed}}, time: time}, config)
when is_list(failed) do
def put_event(
state = %__MODULE__{
failures_counter: failures_counter,
width: width,
tests_counter: tests_counter
},
{:test_finished, test = %ExUnit.Test{state: {:failed, failed}, time: time}}
) when is_list(failed) do
updated_failures_counter = failures_counter + 1
formatted = ExUnit.Formatter.format_test_failure(
test,
failed,
config.failures_counter + 1,
config.width,
updated_failures_counter,
width,
&formatter/2
)
message = Enum.map_join(failed, "", fn {_kind, reason, _stack} -> inspect(reason) end)
Expand All @@ -88,72 +122,63 @@ defmodule TeamCityExUnitFormatter do
duration: div(time, 1000)
)

{
:ok,
%{
config |
tests_counter: config.tests_counter + 1,
failures_counter: config.failures_counter + 1
}
%{
state |
tests_counter: tests_counter + 1,
failures_counter: updated_failures_counter
}
end

def handle_event({:test_finished, test = %ExUnit.Test{state: {:skip, _}}}, config) do
def put_event(
state = %__MODULE__{
tests_counter: tests_counter,
skipped_counter: skipped_counter
},
{:test_finished, test = %ExUnit.Test{state: {:skip, _}}}
) do
attributes = attributes(test)

put_formatted :test_ignored, attributes
put_formatted :test_finished, attributes

{
:ok,
%{
config |
tests_counter: config.tests_counter + 1,
skipped_counter: config.skipped_counter + 1
}
%{
state |
tests_counter: tests_counter + 1,
skipped_counter: skipped_counter + 1
}
end

def handle_event({:test_finished, test = %ExUnit.Test{time: time}}, config) do
def put_event(
state,
{
:test_finished,
test = %ExUnit.Test{
time: time
}
}
) do
put_formatted :test_finished,
test
|> attributes()
|> Keyword.merge(
duration: div(time, 1000)
)

{:ok, config}
state
end

def handle_event({:test_started, test = %ExUnit.Test{tags: tags}}, config) do
def put_event(state, {:test_started, test = %ExUnit.Test{tags: tags}}) do
put_formatted :test_started,
test
|> attributes()
|> Keyword.merge(
locationHint: "file://#{tags[:file]}:#{tags[:line]}"
)

{:ok, config}
end

def handle_event(_, config) do
{:ok, config}
state
end

def init(opts) do
{
:ok,
%{
failures_counter: 0,
invalids_counter: 0,
seed: opts[:seed],
skipped_counter: 0,
tests_counter: 0,
trace: opts[:trace],
width: 80
}
}
end
def put_event(_, state), do: state

## Private Functions

Expand Down
11 changes: 10 additions & 1 deletion src/org/elixir_lang/debugger/xdebug/ElixirXDebugProcess.java
Expand Up @@ -20,7 +20,9 @@

import com.ericsson.otp.erlang.OtpErlangPid;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.ExecutionEnvironment;
Expand Down Expand Up @@ -342,7 +344,14 @@ private OSProcessHandler runDebugTarget() throws ExecutionException {
LOG.debug("Preparing to run debug target.");

ArrayList<String> elixirParams = new ArrayList<>();
elixirParams.addAll(myRunningState.setupElixirParams());
RunnerAndConfigurationSettings runnerAndConfigurationSettings = myExecutionEnvironment.getRunnerAndConfigurationSettings();
RunConfiguration runConfiguration = null;

if (runnerAndConfigurationSettings != null) {
runConfiguration = runnerAndConfigurationSettings.getConfiguration();
}

elixirParams.addAll(myRunningState.setupElixirParams(runConfiguration));
elixirParams.addAll(setUpElixirDebuggerCodePath());

List<String> mixParams = new ArrayList<>();
Expand Down
78 changes: 54 additions & 24 deletions src/org/elixir_lang/exunit/ElixirModules.java
@@ -1,43 +1,73 @@
package org.elixir_lang.exunit;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.ResourceUtil;
import com.intellij.util.io.URLUtil;
import org.elixir_lang.sdk.ElixirSdkRelease;
import org.elixir_lang.sdk.ElixirSdkType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.*;
import java.net.URL;

public class ElixirModules {
private static final String FORMATTER_FILE_NAME = "team_city_ex_unit_formatter.ex";
private static final String MIX_TASK_FILE_NAME = "test_with_formatter.ex";
private static final String FORMATTER_FILE_NAME = "team_city_ex_unit_formatter.ex";
private static final String FORMATTING_FILE_NAME = "team_city_ex_unit_formatting.ex";
private static final String MIX_TASK_FILE_NAME = "test_with_formatter.ex";

private ElixirModules() {
}
private ElixirModules() {
}

private static File putFile(@NotNull String relativePath, @NotNull File directory) throws IOException {
URL moduleUrl = ResourceUtil.getResource(ElixirModules.class, "/exunit", relativePath);

if (moduleUrl == null) {
throw new IOException("Failed to locate Elixir module " + relativePath);
}

try (BufferedInputStream inputStream = new BufferedInputStream(URLUtil.openStream(moduleUrl))) {
File file = new File(directory, relativePath);
file.getParentFile().mkdirs();

private static File putFile(@NotNull String fileName, @NotNull File directory) throws IOException {
URL moduleUrl = ResourceUtil.getResource(ElixirModules.class, "/exunit", fileName);
if (moduleUrl == null) {
throw new IOException("Failed to locate Elixir module " + fileName);
try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file))) {
FileUtil.copy(inputStream, outputStream);
return file;
}
}
}

BufferedInputStream inputStream = new BufferedInputStream(URLUtil.openStream(moduleUrl));
File file = new File(directory, fileName);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
try {
FileUtil.copy(inputStream, outputStream);
return file;
} finally {
inputStream.close();
outputStream.close();
public static File putFormatterTo(@NotNull File directory, @Nullable Project project) throws IOException {
Sdk sdk = null;

if (project != null) {
sdk = ProjectRootManager.getInstance(project).getProjectSdk();
}

return putFormatterTo(directory, sdk);
}
}

public static File putFormatterTo(@NotNull File directory) throws IOException {
return putFile(FORMATTER_FILE_NAME, directory);
}
private static File putFormatterTo(@NotNull File directory, @Nullable Sdk sdk) throws IOException {
String versionDirectory = "1.4.0";

ElixirSdkRelease release = ElixirSdkType.getRelease(sdk);

public static File putMixTaskTo(@NotNull File directory) throws IOException {
return putFile(MIX_TASK_FILE_NAME, directory);
}
if (release != null && release.compareTo(ElixirSdkRelease.V_1_4) < 0) {
versionDirectory = "1.1.0";
}

String source = versionDirectory + "/" + FORMATTER_FILE_NAME;
return putFile(source, directory);
}

public static File putFormattingTo(@NotNull File directory) throws IOException {
return putFile(FORMATTING_FILE_NAME, directory);
}

public static File putMixTaskTo(@NotNull File directory) throws IOException {
return putFile(MIX_TASK_FILE_NAME, directory);
}
}

0 comments on commit b0e8f9a

Please sign in to comment.