Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Test framework for IEx #1137

Merged
merged 13 commits into from
Jun 3, 2013
Merged

Test framework for IEx #1137

merged 13 commits into from
Jun 3, 2013

Conversation

alco
Copy link
Member

@alco alco commented May 29, 2013

This is WIP. Curious to know if I'm moving in the right direction.

TODO

  • write docs for IEx.Case
  • test other helpers

Sorry, something went wrong.

@josevalim
Copy link
Member

@alco this looks great, a couple comments:

  1. Can we drop the itest macro in favor of regular functions? We are doing a bunch of work inside the macro body and that is a no-no. For example, it means we can't for example store a regex in a variable and use it because it expects __r__ and __R__ exactly. Macros should be last resort only. :)

  2. Please move the test framework to test/support/helpers.exs or test/test_helper.exs and call it IEx.Case. That would be a bit more inline with what we do in Mix for example.

@alco
Copy link
Member Author

alco commented May 29, 2013

@mururu Is it feasible to allow capture_io to return output in bursts? For instance, if I want to test a bunch of expressions in one IEx session and one of them is an exception, I need to construct a big regex to check the output.

See this test. I'd like to be able to rewrite it like this:

itest "h helper",
  expected: [%r/{:module,M,<<(.+?)>>,{:hello_world,1}}\n/, 
             "# M\n\nModule M doc:ok\n",
             """* def hello_world()

             Hello world
             * def hello_world(arg)

             Hello world 2
             :ok
             """],
  input:    ["""
    defmodule M do
      @moduledoc "Module M doc"

      @doc "Hello world"
      def hello_world

      @doc "Hello world 2"
      def hello_world(arg)
    end
    """, "h(M)", "h(M.hello_world)"]

So for each item in the input list, we'll get one item in the output. This doesn't necessarily need to be handled by capture_io itself, it could use message passing, for example. Then I would be able to put separate inputs/outputs into lists in IEx.TestFramework.

itest "h helper",
expected: %r/{:module,M,<<(.+?)>>,{:hello_world,1}}\n# M\n\nModule M doc:ok\n\* def hello_world\(\)\n\nHello world\n\* def hello_world\(arg\)\n\nHello world 2\n:ok/,
input: """
defmodule M do
Copy link
Member

Choose a reason for hiding this comment

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

We should cleanup a module after we dynamically define it in a test. :) Or for this case, couldn't we just call h in an existing module? Maybe defined those in IEx.Test.Interactive itself.

Copy link
Member Author

Choose a reason for hiding this comment

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

Using docs on the test module is a great idea.

Copy link
Member Author

Choose a reason for hiding this comment

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

Does ExDoc skip modules in test directories?

@alco
Copy link
Member Author

alco commented May 29, 2013

For example, it means we can't for example store a regex in a variable and use it because it expects r and R exactly.

@josevalim This is intentional. You can also use functions if you like. For example, those tests are equivalent:

  itest "color",
    opts:     [colors: [eval_result: "red"]],
    input:    "1 + 2",
    expected: "\e[31m3\e[0m"

  test "color redux" do
    assert (capture_iex("1 + 2", [colors: [eval_result: "red"]])) == "\e[31m3\e[0m"
  end

I find the first one more preferable because it gives you a framework to fill the values in. If you need to test something tricky, you can use capture_iex directly.

use IEx.TestFramework

# All tests that `use IEx.TestFramework` should go into this module to avoid
# race conditions with :iex application.
Copy link
Member

Choose a reason for hiding this comment

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

We are not going to have race conditions if all the modules are sync.

Copy link
Member Author

Choose a reason for hiding this comment

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

When I create another module and use IEx.TestFramework there, I get the {:error,{:already_started,:iex}} error when running tests.

Copy link
Member

Choose a reason for hiding this comment

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

I will need to take a better look then... but the test cases were meant to run on isolation. Unless :application.stop is async (i.e. when it returns, the application may not have stopped completely yet, which I don't think is the case. :)

Copy link
Member Author

Choose a reason for hiding this comment

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

My bad, I've written def teardown_all do instead of teardown_all do.

@josevalim
Copy link
Member

@alco I am still not sold why we need macros here. Compare:

  itest "color",
    opts:     [colors: [eval_result: "red"]],
    input:    "1 + 2",
    expected: "\e[31m3\e[0m"

To:

  test "color" do
    opts = [colors: [eval_result: "red"]]
    assert capture_iex("1 + 2", opts) == "\e[31m3\e[0m"
  end

There isn't really a benefit and everyone knows exactly how the second one works. There is no gotchas about when to use regular expressions, which expressions are supported by the macro, etc...

@alco
Copy link
Member Author

alco commented May 29, 2013

@josevalim Here are the same tests rewritten using test -- https://github.com/elixir-lang/elixir/pull/1137/files#diff-4

Does it look good?

@mururu
Copy link
Contributor

mururu commented May 29, 2013

@alco It is possible to allow capture_io to return output each time it receives an output request(:get_chars, :get_line and :get_until). But I don't know it is useful because one function might send many output request. In case of the example, h(M.hello_world) sends more than one output requests so the outputs will be divided into more than you expect.

@alco
Copy link
Member Author

alco commented May 29, 2013

@mururu Thanks for the feedback.

@alco
Copy link
Member Author

alco commented May 29, 2013

I've moved things around and renamed the modules. If it's OK, I will only need to add docs and tests for the remaining helpers.

@josevalim
Copy link
Member

@alco this is perfect! Before you start working on the rest, do you mind squashing those commits together so we have the whole foundation set up in one single commit? Easier to visualize latter.

@josevalim josevalim mentioned this pull request May 30, 2013
@alco
Copy link
Member Author

alco commented May 30, 2013

@josevalim Please take another look. Tests for l and r are currently failing. I need your help with explaining the behavior marked as FIXME in the code.

@@ -0,0 +1,28 @@
Code.require_file "../test_helper.exs", __DIR__

defmodule IEx.Options.Test do
Copy link
Member

Choose a reason for hiding this comment

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

IEx.OptionsTest :)

@alco
Copy link
Member Author

alco commented May 30, 2013

@josevalim I've rewritten the tests using capture_io when appropriate.

Please take a look at FIXMEs in the tests for l helper and r helper.

@alco
Copy link
Member Author

alco commented May 30, 2013

There appears to be some kind race condition between the tests. Contrary to what Travis shows, this is what I get:
screen shot 2013-05-30 at 19 13 06

@josevalim
Copy link
Member

We also need to test this one... we should just not forget... 6397ced

@josevalim
Copy link
Member

Basically, to reproduce it, we just need to type something like "%B(%{bar})" in the shell and it should not fail.

@alco
Copy link
Member Author

alco commented May 31, 2013

I've added tests for color, but the aren't working. Colors are working correctly in IEx, but after capture_io some escapes are lost.

@alco
Copy link
Member Author

alco commented May 31, 2013

Dough, I see what's wrong: capture_io is not treated as terminal, so IO.ANSI.escape disables coloring.

@alco
Copy link
Member Author

alco commented Jun 1, 2013

This is almost ready. Once the helpers issues are resolved, I'd like to squash some commits before merging.

File.rm! "Elixir.Helpers_test_module.beam"

# FIXME: This errors out with "Module 'Elixir.Helpers_test_module' must be purged before loading"
#true = :code.delete Helpers_test_module
Copy link
Member

Choose a reason for hiding this comment

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

The problem is in this commented line. When you call the l helper, it purges the old version and then loads a new version. By loading a new version, the previous version is now marked as old. So when you get to :code.delete to make the current new version as old, you can't actually do it, because the old version slot is still taken, so you do need to explicitly purge once again before. By fixing this, everything else seems to work fine (except the r test that still fails).

@josevalim
Copy link
Member

@alco a couple quick notes: the test fixtures uses non conventional names like Helpers_test_module and test-foo-bar. Maybe they should follow Elixir's conventions too? Also, it may be worthy creating a couple local functions to clean up the helper tests, like a purge(module) (to delete and purge) and another one called with_helpers_test_module(callback) that would wrap the writing to a file and removing the file.

@alco
Copy link
Member Author

alco commented Jun 1, 2013

Thanks, I'll fix those.

What about this line? That System.cmd call would better go away.

@josevalim
Copy link
Member

Use the parallel compiler or c helper and then purge and delete the module?

José Valim
www.plataformatec.com.br
Founder and Lead Developer
*

@alco
Copy link
Member Author

alco commented Jun 2, 2013

the test fixtures uses non conventional names like Helpers_test_module and test-foo-bar

I renamed the module to Sample. What name should I use for the file?

@alco
Copy link
Member Author

alco commented Jun 2, 2013

Use the parallel compiler or c helper and then purge and delete the module?

I'd like to not interfere with the modules loaded in runtime while testing module loading. Is it technically not possible to compile a module from Erlang without loading it?

@josevalim
Copy link
Member

Give the file the name of the module, like sample.ex. About the r helper,
feel free to change the docs or the return value. The important is that we
properly test it from now on. :)

José Valim
www.plataformatec.com.br
Founder and Lead Developer
*

@alco
Copy link
Member Author

alco commented Jun 3, 2013

@josevalim Please take another look.

@@ -257,6 +264,7 @@ defmodule IEx.Helpers do
"""
def l(module) do
:code.purge(module)
# FIXME: shouldn't we use Code.load_file here?
Copy link
Member

Choose a reason for hiding this comment

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

No. They do different things. We want to continue using :code.load_file here.

@josevalim
Copy link
Member

@alco it looks good! we should just get rid of the #FIXME that have been answered (we should use :code.load_file and there isn't a way of compiling a module without loading it) and then pls go ahead and merge it.

alco added a commit that referenced this pull request Jun 3, 2013
@alco alco merged commit 75a1373 into elixir-lang:master Jun 3, 2013
@alco alco deleted the 919-iex-test-fmwk branch May 16, 2014 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants