Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >the-solution-of-elixir-continuous-runtime-system-code-coverage-collection

the-solution-of-elixir-continuous-runtime-system-code-coverage-collection

作者头像
Cloud-Cloudys
发布于 2023-10-21 02:19:07
发布于 2023-10-21 02:19:07
20300
代码可运行
举报
运行总次数:0
代码可运行

zh_hans

Code coverage is an effective means to assist software engineers in verifying code quality. The runtime environment’s ability to collect code coverage fully combines black and white box testing capabilities and greatly increases engineers’ confidence in software quality. This article introduces a solution for code coverage collection in the Elixir runtime environment, and provides an in-depth insight into its internal principles.

1. Brief talk on code coverage

As a SDET or a SWE, we often need to write unit tests or integration test cases to verify the correctness of the system/application, but at the same time we often question whether our tests are adequate. At this time, test coverage is a means of measuring the adequacy of our testing, enhancing the success rate and confidence of the software release, and giving us more reflective perspectives. The note of the value is that high code coverage does not indicate high code quality, but conversely, code coverage is low, and code quality will not be high.

Most programming languages come with the ability to collect unit test coverage, and the same is true for Elixir, the official mix build tool comes with the ability to collect coverage, but it is currently only suitable for offline system, not for runtime system. This article will be based on Erlang’s cover module to give a solution for the Elixir runtime system. Since cover is Erlang’s built-in module, but why it works equally well with Elixir, we’ll unveil its mystery in a follow-up. Before we get started, let’s take a look at the two mainstream ways in which the open source community collects code coverage at runtime (here we look at the bytecode stubbing method of Java, which has a huge ecosystem of the language community):

Next let’s focus on the core of elixir runtime coverage collection in this article - the cover module.

2. Delve into the Erlang Cover coverage collection implementation mechanism

2.1. Introduction Erlang Cover

cover is part of Erlang’s built-in tools set, providing a powerful ability to collect code coverage.

2.2. Erlang code coverage collection implementation analysis

As you can see from Erlang’s official manual of the cover module, cover counts the number of times every executable line in the Erlang program is executed.

From the introduction of the official documentation, cover can be used for code coverage collection of the runtime system. When the code is instrumented, it does not modify the code source files of any modules or the beam files generated after compilation (that is the industry calls the On-The-Fly mode). Every time the executable row is called, the runtime system updates the number of calls to cover in an in-memory database (erlang ets) for storing data for subsequent coverage analysis.

Next, we’ll explore the details of the On-The-Fly mode under cover.

2.3. Learn about BEAM File Format

Before we can further understand the details of the cover implementation, it is necessary to understand the format of the BEAM file after the elixir source code is compiled. The compiled product of the Elixir (.ex file), like the Erlang (.erl file), is a binary chunked file, which is divided into several sections to store information used when the program runs (such as virtual machine operation instructions). In Erlang/Elixir, each module will have a corresponding BEAM file. The approximate structure of the BEAM file is as follows:

Let’s take a look at the approximate content of the beam file through an Elixir mini demo project:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
git clone https://github.com/yeshan333/explore_ast_app.git
cd explore_ast_app
  • Step 2. Build this project in OTP release format. (note: Elixir and Erlang need to be installed locally):
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MIX_ENV=prod mix distillery.release

It can be noted that each Elixir module is compiled into a BEAM file (can be seen in the directory _build/prod/rel/explore_ast_app/lib/explore_ast_app-0.1.0/ebin).

  • Step 3. Next, let’s view the chunks in the Beam file through Erlang’s standard library beam_lib:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# open the iex console
iex -S mix

View all chunks of the compiled BEAM file (Elixir.ExploreAstApp.beam):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ iex -S mix
Erlang/OTP 24 [erts-12.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit] [dtrace]

Interactive Elixir (1.12.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> beam_file_path = "_build/prod/rel/explore_ast_app/lib/explore_ast_app-0.1.0/ebin/Elixir.ExploreAstApp.beam"
"_build/prod/rel/explore_ast_app/lib/explore_ast_app-0.1.0/ebin/Elixir.ExploreAstApp.beam"
iex(2)> all_chunks = :beam_lib.all_chunks(String.to_charlist(beam_file_path))
{:ok, ExploreAstApp,
 [
   {'AtU8',
    <<0, 0, 0, 15, 20, 69, 108, 105, 120, 105, 114, 46, 69, 120, 112, 108, 111,
      114, 101, 65, 115, 116, 65, 112, 112, 8, 95, 95, 105, 110, 102, 111, 95,
      95, 10, 97, 116, 116, 114, 105, 98, 117, 116, 101, ...>>},
   {'Code',
    <<0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 169, 0, 0, 0, 14, 0, 0, 0, 4, 1, 16,
      153, 0, 2, 18, 34, 16, 1, 32, 59, 3, 21, 23, 8, 16, 50, 117, 66, 117, 82,
      101, 98, ...>>},
   {'StrT', ""},
   {'ImpT',
    <<0, 0, 0, 2, 0, 0, 0, 11, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 11, 0, 0, 0,
      12, 0, 0, 0, 1>>},
   {'ExpT',
    <<0, 0, 0, 4, 0, 0, 0, 15, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 15, 0, 0, 0, 0,
      0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 9, ...>>},
   {'LitT',
    <<0, 0, 0, 52, 120, 156, 99, 96, 96, 96, 98, 96, 96, 16, 106, 206, 1, 146,
      140, 25, 76, 229, 172, 25, 169, 57, 57, 249, 137, 12, 89, 64, 190, 88,
      115, 46, 144, 20, 248, ...>>},
   {'LocT', <<0, 0, 0, 0>>},
   {'Attr',
    <<131, 108, 0, 0, 0, 1, 104, 2, 100, 0, 3, 118, 115, 110, 108, 0, 0, 0, 1,
      110, 16, 0, 165, 236, 94, 47, 119, 160, 184, 33, 240, 28, 89, 11, 22, 130,
      207, ...>>},
   {'CInf',
    <<131, 108, 0, 0, 0, 3, 104, 2, 100, 0, 7, 118, 101, 114, 115, 105, 111,
      110, 107, 0, 5, 56, 46, 48, 46, 51, 104, 2, 100, 0, 7, 111, 112, 116, 105,
      111, ...>>},
   {'Dbgi',
    <<131, 80, 0, 0, 1, 143, 120, 156, 117, 80, 203, 78, 3, 49, 12, 76, 233, 67,
      162, 5, 113, 65, 124, 70, 87, 253, 2, 212, 67, 63, 129, 115, 148, 221,
      120, ...>>},
   {'Docs',
    <<131, 80, 0, 0, 0, 241, 120, 156, 93, 142, 205, 10, 194, 48, 16, 132, 215,
      74, 91, 9, 248, 14, 129, 94, 244, 82, 241, 234, 65, 40, 88, 241, 45, 108,
      ...>>},
   {'ExCk',
    <<131, 104, 2, 100, 0, 17, 101, 108, 105, 120, 105, 114, 95, 99, 104, 101,
      99, 107, 101, 114, 95, 118, 49, 116, 0, 0, 0, 1, 100, 0, 7, 101, 120,
      ...>>},
   {'Line',
    <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 18, 241, 0,
      22, 108, 105, 98, 47, 101, 120, 112, 108, ...>>}
 ]}

As you can see, the obtained chunks correspond to the previous diagram. We can also obtain the Erlang AST (abstract syntax tree) corresponding to the module (ExploreAstApp) through the beam_lib standard library:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
iex(3)> result = :beam_lib.chunks(String.to_charlist(beam_file_path), [:abstract_code])
{:ok,
 {ExploreAstApp,
  [
    abstract_code: {:raw_abstract_v1,
     [
       {:attribute, 1, :file, {'lib/explore_ast_app.ex', 1}},
       {:attribute, 1, :module, ExploreAstApp},
       {:attribute, 1, :compile, [:no_auto_import]},
       {:attribute, 1, :export, [__info__: 1, hello: 0]},
       {:attribute, 1, :spec,
        {{:__info__, 1},
         [
           {:type, 1, :fun,
            [
              {:type, 1, :product,
               [
                 {:type, 1, :union,
                  [
                    {:atom, 1, :attributes},
                    {:atom, 1, :compile},
                    {:atom, 1, :functions},
                    {:atom, 1, :macros},
                    {:atom, 1, :md5},
                    {:atom, 1, :exports_md5},
                    {:atom, 1, :module},
                    {:atom, 1, :deprecated}
                  ]}
               ]},
              {:type, 1, :any, []}
            ]}
         ]}},
       {:function, 0, :__info__, 1,
        [
          {:clause, 0, [{:atom, 0, :module}], [], [{:atom, 0, ExploreAstApp}]},
          {:clause, 0, [{:atom, 0, :functions}], [],
           [
             {:cons, 0, {:tuple, 0, [{:atom, 0, :hello}, {:integer, 0, 0}]},
              {nil, 0}}
           ]},
          {:clause, 0, [{:atom, 0, :macros}], [], [nil: 0]},
          {:clause, 0, [{:atom, 0, :exports_md5}], [],
           [
             {:bin, 0,
              [
                {:bin_element, 0,
                 {:string, 0,
                  [240, 105, 247, 119, 22, 50, 219, 207, 90, 95, 127, 92, ...]},
                 :default, :default}
              ]}
           ]},
          {:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :attributes}}],
           [],
           [
             {:call, 0,
              {:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
              [{:atom, 0, ExploreAstApp}, {:var, 0, :Key}]}
           ]},
          {:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :compile}}], [],
           [
             {:call, 0,
              {:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
              [{:atom, 0, ExploreAstApp}, {:var, 0, :Key}]}
           ]},
          {:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :md5}}], [],
           [
             {:call, 0,
              {:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
              [{:atom, 0, ExploreAstApp}, {:var, 0, :Key}]}
           ]},
          {:clause, 0, [{:atom, 0, :deprecated}], [], [nil: 0]}
        ]},
       {:function, 15, :hello, 0, [{:clause, 15, [], [], [{:atom, 0, :world}]}]}
     ]}
  ]}}

It can be seen that AST is expressed in the form of Erlang Terms (called Abstract Code), which is easy to read. The Abstract Code is very useful in the on-the-fly instrumentation process of cover.

The above AST structure is simple and easy to read, and we can easily match it with the source code (lib/explore_ast_app.ex) before the module is compiled, although the AST structure is the final Erlang AST, and some extras information are added by the Erlang compiler, but does not affect reading:

The second element in the tuple generally represents the number of source code lines. You can learn more about Erlang’s Abstract Format through the official documentation. By observing the Erlang AST structure of several BEAM files, you will be familiar with it. It is worth noting that the Abstract Code was stored in the Abstract Chunk of the BEAM file before OTP 20.

If you want to learn more about BEAM files in detail, you can check out the following two documents:

2.4. Elixir source code compilation process

After understanding BEAM File Format, we also need to understand the compilation process of Elixir code, which will help us better understand cover. The process of compiling Elixir source code into BEAM file may not be as you imagined In the same way, instead of directly from Elixir’s AST, it becomes executable BEAM Code after being processed by the compiler backend. There is also a process in the middle, as shown in the following figure:

The above process can be described as:

  • Step 1、The Elixir source code will be parsed by a custom lexical analyzer (elixir_tokenizer) and yacc to generate the initial version of Elixir AST, which is expressed in the form of Elixir Terms; if you are interested in Elixir’s AST, you can follow this Project arjan/ast_ninja;
  • Step 2、In the Elixir AST stage, some custom and built-in Macros have not been expanded, and these Macros are expanded into the final Elixir AST in the Expanded Elixir AST stage;
  • Step 3、Final Elixir AST will be converted into Erlang standard AST form (Erlang Abstract Format) after being processed by Elixir Compiler;
  • Step 4、Finally, Elixir will use the Erlang Compiler to process the Erlang AST, converting it into BEAM bytecode executable by the BEAM Virtual Machine (VM). For details on the compiler, see: elixir_compiler.erl and elixir_erl.erl source code For more details on the Erlang Compiler, see theBeamBook/#CH-Compiler.

2.5. Cover On-The-Fly Instrumentation Implementation

Now it’s time for dinner. Let’s see how cover performs instrumentation and coverage collection. To use cover to complete code coverage collection, we must know three dragon-slaying swords:

  • cover:start: Used to create the cover coverage collection process, it will complete the creation of the relevant ets table to store the coverage data, cover.erl#L159 & cover.erl#L632, and we can also start the cover process of the remote Erlang node.
  • cover:compile_beam: For instrumentation, cover will read the content of the abstract_code of the BEAM file, namely Erlang AST. The key code is in cover.erl#L1541, and then transform and munge the Erlang AST From, it will call bump_call, after each executable line will insert the following abstract_code:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{call,A,{remote,A,{atom,A,ets},{atom,A,update_counter}},
 [{atom,A,?COVER_TABLE},
  {tuple,A,[{atom,A,?BUMP_REC_NAME},
            {atom,A,Vars#vars.module},
            {atom,A,Vars#vars.function},
            {integer,A,Vars#vars.arity},
            {integer,A,Vars#vars.clause},
            {integer,A,Line}]},
  {integer,A,1}]}.

From the previous understanding of Erlang AST, we know that this is equivalent to inserting the following line of code:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ets:update_counter(?COVER_TABLE, #bump{module=Module, function=Function, arity=Arity, clause=Clause, line=Line}, 1).

Then for the mungeed Erlang AST Form, cover uses the Erlang Compiler to obtain the Erlang Beam Code (also known as object code. i.e. bytecode, VM execution instructions) from the mungeed AST expression form. cover.erl#L1580. And then use the Erlang code server to replace the old object code with the new object code obtained, load_binary cover.erl#L1581 into ERTS (Erlang Run Time System). cover completes the Erlang AST instrumentation process, so that whenever the executable line is Executed, the corresponding ets storage table will update the number of times the code line was called.

  • cover:analyze: Analyze the data stored in the ets table to obtain the number of times the executable line was executed (called), which can be used for statistical coverage data.

munge: Used to make a series of potentially destructive or irreversible changes to data or files.

3. Elixir Application runtime coverage collection example

Through the above, after understanding the implementation details of the Erlang Cover module. Let us take a deployed and running Elixir Application (we will use the previous yesan333/explore_ast_app) as an example to perform large-scale tests (system & integration tests) of the Elixir application runtime of code line-level coverage collection.

Here we will use a tool library: ex_integration_coveralls for coverage analysis, which is an Elixir Wrapper for the Erlang module cover to collection Elixir runtime system coverage. Let’s start:

  • Step 1、Add ex_integration_coveralls dependency to mix.exs file:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
defp deps do
  [
    ...,
    {:ex_integration_coveralls, "~> 0.3.0"}
  ]
end

Pull the dependencies and rebuild the project:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mix deps.get
MIX_ENV=prod mix distillery.release
  • Step 2、Start the project:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_build/prod/rel/explore_ast_app/bin/explore_ast_app foreground
  • Step 3、Connect to the remote_console of the Elixir runtime application node:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_build/prod/rel/explore_ast_app/bin/explore_ast_app remote_console
  • Step 4、Use ex_integration_coveralls (ExIntegrationCoveralls.execute) to start cover and perform code coverage collection:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
iex(explore_ast_app@127.0.0.1)1> compiled_beam_dir_path = "/Users/yeshan/oss_github/explore_ast_app/_build/prod/rel/explore_ast_app/lib/explore_ast_app-0.1.0/ebin"
"/Users/yeshan/oss_github/explore_ast_app/_build/prod/rel/explore_ast_app/lib/explore_ast_app-0.1.0/ebin"
iex(explore_ast_app@127.0.0.1)2> ExIntegrationCoveralls.execute(compiled_beam_dir_path)
[
  ok: ExploreAstApp.Router,
  ok: ExploreAstApp.Plug.VerifyRequest.IncompleteRequestError,
  ok: ExploreAstApp.Plug.VerifyRequest,
  ok: ExploreAstApp.Application,
  ok: ExploreAstApp
]
iex(explore_ast_app@127.0.0.1)3> compile_time_source_lib_abs_path = "/Users/yeshan/oss_github/explore_ast_app"
"/Users/yeshan/oss_github/explore_ast_app"
iex(explore_ast_app@127.0.0.1)4> source_code_abs_path = "/Users/yeshan/oss_github/explore_ast_app"
"/Users/yeshan/oss_github/explore_ast_app"
iex(explore_ast_app@127.0.0.1)5> ExIntegrationCoveralls.get_total_coverage(compile_time_source_lib_abs_path, source_code_abs_path)
0

As you can see, the initial coverage is 0, because no code has been called yet.

  • Step 5、Let’s execute the following cURL. Let code be called:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ curl --location --request GET 'http://localhost:8080/hello'
hello %

Check out the code coverage data again in iex console:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
iex(explore_ast_app@127.0.0.1)6> ExIntegrationCoveralls.get_total_coverage(compile_time_source_lib_abs_path, source_code_abs_path)
17.1

As you can see, the cURL (test case) coverage for this project is 17.1%.

We can also use the following methods to view more detailed code coverage, such as viewing the code coverage of lib/explore_ast_app/router.ex (nil means the line is not an executable line):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
iex(explore_ast_app@127.0.0.1)7> result = ExIntegrationCoveralls.get_coverage_report(compile_time_source_lib_abs_path, source_code_abs_path)
.......
iex(explore_ast_app@127.0.0.1)8> Enum.at(Map.get(result, :files), 3)
%ExIntegrationCoveralls.Stats.Source{
  coverage: 18.2,
  filename: "lib/explore_ast_app/router.ex",
  hits: 4,
  misses: 18,
  sloc: 22,
  source: [
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 1,
      source: "defmodule ExploreAstApp.Router do"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: 1, source: "  use Plug.Router"},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "  use Plug.ErrorHandler"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: ""},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "  import Plug.Conn"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: ""},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "  alias ExploreAstApp.Plug.VerifyRequest"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: ""},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "  plug Plug.Parsers, parsers: [:urlencoded, :multipart]"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "  plug VerifyRequest, fields: [\"content\", \"mimetype\"], paths: [\"/upload\"]"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: "  plug :match"},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "  plug Plug.Parsers, parsers: [:json], pass: [\"application/json\"], json_decoder: Jason"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "  plug :dispatch"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: ""},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 0,
      source: "  get \"/welcome\" do"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 0,
      source: "    send_resp(conn, 200, \"Welcome\")"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: "  end"},
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: ""},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 0,
      source: "  get \"/upload\" do"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 0,
      source: "    send_resp(conn, 201, \"Uploaded\")"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: "  end"},
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: ""},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 1,
      source: "  get \"/hello\" do"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "    # query parameter is user like this:"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "    # http://localhost:4001/hello?name=John"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "    # which will create %{\"name\" => \"John\"}"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 1,
      source: "      send_resp(conn, 200, \"hello \#{Map.get(conn.query_params, \"name\")}\")"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: "  end"},
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: ""},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 0,
      source: "  get \"/hello/:name\" do"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 0,
      source: "    send_resp(conn, 200, \"hello \#{name}\")"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: "  end"},
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: ""},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 0,
      source: "  post \"/hello\" do"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "    # json body of POST request {\"name\": \"John\"} is parsed to %{\"name\" => \"John\"}"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "    # so it can be accesable with e.g. Map.get(conn.body_params, \"name\") or with pattern matching"
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: 0, source: "    name ="},
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 0,
      source: "      case conn.body_params do"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: 0,
      source: "        %{\"name\" => a_name } -> a_name"
    },
    %ExIntegrationCoveralls.Stats.Line{
      coverage: nil,
      source: "        _ -> \"\""
    },
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: "      end"},
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, source: ""},
    %ExIntegrationCoveralls.Stats.Line{coverage: nil, ...},
    %ExIntegrationCoveralls.Stats.Line{...},
    ...
  ]
}

Based on the post_cov_stats_to_ud_ci interface, it is possible to further interface with internal or external Codecov-like coverage systems.

Based on this, we can realize the collection of code coverage with large-scale (integration & system) testing capabilities without stopping the Elixir Application.

4. Continuous runtime coverage collection solution for large-scale Elixir/Erlang Microservice clusters

With the continuous expansion of the Elixir/Erlang microservice system, the coverage collection method shown in the previous section needs further evolution. Referring to the design of Prometheus Pull-Base, the overall design (combination of Pull & Push mode) is as follows:

We expand based on ex_integration_coveralls. After the Elixir Application is started, a http worker is started up to expose the code coverage data in real time, which is convenient for communication with heterogeneous systems. The Coverage Push Gateway is responsible for regularly pulling the coverage data (Gateway can be a OTP Application, which allows ex_integration_coveralls to directly start up the custom GenServer Worker for interactive integration test system in the distributed OTP system), after the integration/system test system informs the end of the test, the Gateway pushes the coverage data to the Cover Center for code coverage rate display.

End (long way to go).

References

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-8-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Elixir 连续运行时代码覆盖率采集方案
作为 SET 和 SWE, 我们经常需要编写单元测试或集成测试用例来验证系统/应用的正确性, 但同时我们也常会质疑我们的测试是否充分了. 这时测试覆盖率是可以辅助用来衡量我们测试充分程度的一种手段, 增强发布成功率与信心, 同时给了我们更多可思考的视角. 值的注意的是代码覆盖率高不能说明代码质量高, 但是反过来看, 代码覆盖率低, 代码质量不会高到哪里去.
Cloud-Cloudys
2023/10/21
4190
Elixir 连续运行时代码覆盖率采集方案
Elixir 依赖 (deps) 调试的小技巧
最近使用 Elixir 有点多, 经常需要观察一些依赖 (Deps) 的实现, 比如想加个日志打印点 IO.inspect 啥的观察下某个变量,才能更好的理解某个 Elixir 的依赖。这里介绍下一些调试的方式:
Cloud-Cloudys
2023/10/21
2340
Elixir 依赖 (deps) 调试的小技巧
(译) Understanding Elixir Macros, Part 6 - In-place Code Generation
这是宏系列文章的最后一篇. 在开始之前, 我想提一下 Björn Rochel, 他已经将他的 Apex 库中的 deftraceable 宏改进了. 因为他发现系列文章中 deftraceable 的版本不能正确处理默认参数(arg \ def_value), 于是实现了一个修复 fix.
Cloud-Cloudys
2023/10/21
2140
(译) Understanding Elixir Macros, Part 4 - Diving Deeper
在前一篇文章中, 我向你展示了分析输入 AST 并对其进行处理的一些基本方法. 今天我们将研究一些更复杂的 AST 转换. 这将重提已经解释过的技术. 这样做的目的是为了表明深入研究 AST 并不是很难的, 尽管最终的结果代码很容易变得相当复杂, 而且有点黑科技(hacky).
Cloud-Cloudys
2023/10/21
1420
(译) Understanding Elixir Macros, Part 2 - Micro Theory
这是 Elixir 中的宏系列的第二篇. 上一次我们讨论了编译过程和 Elixir AST, 最后讲了一个基本的宏的例子 trace. 今天, 我们会更详细地讲解宏的机制.
Cloud-Cloudys
2023/10/21
2200
(译) Understanding Elixir Macros, Part 1 Basics
这是讨论宏 (Macros) 微系列文章的第一篇. 我原本计划在我即将出版的《Elixir in Action》一书中讨论这个主题, 但最终决定不这么做, 因为这个主题不符合这本书的主题, 这本书更关注底层 VM 和 OTP 的关键部分.
Cloud-Cloudys
2023/10/21
2300
(译) Understanding Elixir Macros, Part 1 Basics
(译) Understanding Elixir Macros, Part 5 - Reshaping the AST
上次我介绍了一个基本版本的可追溯宏 deftraceable, 它允许我们编写可跟踪的函数. 这个宏的最终版本还有一些遗留的问题, 今天我们将解决其中一个 — 参数模式匹配.
Cloud-Cloudys
2023/10/21
1700
通过 vfox 安装管理多版本 Erlang 和 Elixir
vfox (version-fox) 是最近比较热门的一个通用版本管理工具,使用 Go 语言进行编写,插件机制使用了 Lua 去实现扩展性. 目前 vfox 已经支持管理大多数主流编程语言的版本,生态还算强大。在这里你可以看到目前 vfox 所支持管理的编程语言版本和工具 -> vfox-Available Plugins
Cloud-Cloudys
2024/04/28
2220
通过 vfox 安装管理多版本 Erlang 和 Elixir
rebar3-配置
1. 全局(所有命令)配置 ---- rebar3支持一些系统环境变量 变量设置 解释 REBAR_PROFILE="term" 强制使用基础配置 HEX_CDN="https://..." CDN端点设置 REBAR_CONFIG="rebar3.config" 修改rebar配置文件名称 QUIET=1 只输出错误信息 DEBUG=1 输出DEBUG信息 REBAR_COLOR="low" 如果支持,减少输出信息的颜色数量 2. Alias(别名) ---- 别名允许你根据现有命令,创造一个新的命令出
山海散人
2021/03/03
1.6K0
CentOS6 mininal 安装CouchDB2 详细版
shaonbean
2018/01/02
1.9K0
CentOS6 mininal 安装CouchDB2 详细版
颠覆者的游戏:程序语言
程序语言的用户是程序员,她们对语言的要求不外乎是:好用,没bug,能快速开发,容易写出高质量的代码,性能好,可伸缩,容易部署,生态圈完备,blablabla。所以程序语言这块广阔的市场的价值主张也主要围绕着这些需求展开。这个市场和其他赢者通吃的互联网市场来说,不太一样,第一的占有者也就是维持着不到20%的头部,长尾一路延伸到几百名开外。 最初程序语言围绕着性能,以及同样量级的性能下的易用性做价值主张。和硬件结合紧密的C是最大的赢家。市场上的语言们都紧盯着C,编译器的发展方向也是性能,性能,性能。 90年代中
tyrchen
2018/03/28
1K0
Elixir, OTP, Ecto, 和 Phoenix 免费教程!
今天,DailyDrip发布了五周的免费内容,向人们介绍Elixir编程语言,并准备使用Ecto和Phoenix构建Web应用程序。我们制作了25个短片(每个约5分钟),这将使你从“Elixir是什么
时见疏星
2018/06/01
1.9K0
RabbitMQ基础6
关闭RabbitMQ 应用 [root@h102 rabbitmq]# rabbitmqctl status Status of node rabbit@h102 ... [{pid,5596}, {running_applications,[{rabbit,"RabbitMQ","3.5.6"}, {os_mon,"CPO CXC 138 46","2.2.7"}, {xmerl,"XML parser"
franket
2022/05/04
3110
Golang基于Gitlab CI/CD部署方案
持续集成 (Continuous integration)是一种软件开发实践,即团队开发成员经常集成它们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。
李海彬
2018/12/24
1.5K0
How to find a good java software development company?
Java software development has been in operations for almost 3 decades yet it is evolving. The exorbitant amounts of digital marketing campaigns run by software development companies have made it even more difficult to find a good java software development company. Even if you seek out the guidance and interview questions that will allow you to filter out a good java software development company, the search engines will bombard you with web pages of these software development service providers and ours might appear in it as well.
用户4822892
2020/04/09
4960
How to find a good java software development company?
rebar3-命令
每个命令代表一个任务,运行一个或多个功能完成任务。 1. as ---- 高阶任务,它使一个配置文件名称和任务列表在该配置文件下运行。 2. compile ---- 在确保所有依赖项都可用之后,如果依赖项不存在,会获取依赖项,该命令将编译所需的依赖项和项目的应用程序的.app.src文件和.erl文件。 3. clean ---- 从应用程序中删除已编译生成的beam文件。 默认情况下,clean命令删除顶级应用程序的beam文件。对于配置文件的处理也是如此,这意味着rebar3 clean仅仅清
山海散人
2021/03/03
1.7K0
【Rabbitmq报错及解决办法】Error: unable to connect to node rabbit@rabbitmq3: nodedown
参考链接 报错信息如下: [root@rabbitmq3 rabbitmq]# rabbitmqctl stop_app Stopping node rabbit@rabbitmq3 ... Error: unable to connect to node rabbit@rabbitmq3: nodedown DIAGNOSTICS =========== attempted to contact: [rabbit@rabbitmq3] rabbit@rabbitmq3: * connected
宝耶需努力
2022/12/13
1.4K0
Erlang入门路线
间歇性的学了一些Erlang,写了一个直播cdn网关的程序,也算是贡献了代码,完成了第一个项目。结束之际写一个入门路线,记录学习过程。
职场亮哥
2020/10/10
2.2K0
Golang基于Gitlab CI/CD部署方案
持续集成 (Continuous integration)是一种软件开发实践,即团队开发成员经常集成它们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。
李海彬
2018/09/29
3K0
Golang基于Gitlab CI/CD部署方案
04-基于CentOS7安装RabbitMQ3.10.7
我实在是找不到这么老的版本了, 直接用最新版本的, 按照道理来说, 新版本是兼容老版本的
彼岸舞
2022/10/06
7020
04-基于CentOS7安装RabbitMQ3.10.7
相关推荐
Elixir 连续运行时代码覆盖率采集方案
更多 >
加入讨论
的问答专区 >
1高级后端开发工程师擅长3个领域
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    本文部分代码块支持一键运行,欢迎体验
    本文部分代码块支持一键运行,欢迎体验