前往小程序,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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
腾讯携手九大博物馆,用青年创意活化传统文化
在腾讯,有一群人热衷“捣古”,古代的古。 他们造访故宫,宫里的美人们都骚动了。 「姑苏城外寒山寺,夜半钟声画新番。」 他们想到歌,就让古画唱起了歌。 「客堂淸话」 联合音乐人张亚东、方文山、易烊千玺专为《千里江山图》打造了一曲《丹青千里》,随着歌声仿佛置身画中,游历江山。 「雨霁归帆」 点击文末阅读原文回顾完整版《丹青千里》 他们想学工艺,便有了以中国传统建筑工艺 “榫卯” 为主题制作的智趣游戏《第五大发明》。 他们用一些古的idea(才不是小编英语发音不好)让传统文化从封尘的历史中
腾讯文旅
2020/06/17
8650
青年和技术,如何改变了博物馆?|2025国际博物馆日
2025年国际博物馆日以“快速变化社会中的博物馆未来”(The Future of Museums in Rapidly Changing Communities)为主题,聚焦博物馆如何在社会、科技和环境加速变革的当下持续发挥影响力,凝聚共识。这不仅是对博物馆传统使命的肯定,更是对其未来发展方向的前瞻。
小腾资讯君
2025/05/19
1830
3000万人抢3500张票 故宫元宵灯会开启夜游经济新玩法
“去故宫吗?” “没有票,去不了啊,太难抢了!” 这大概是昨天朋友圈里聊天记录中出现频率最多的对话了。 2019年元宵节,故宫举办“紫禁城上元之夜”活动,这是故宫建院94年来首次在晚间免费对公众开放! 活动免费,但需网上预约。消息一出,活动门票迅速被抢空,抢票激烈程度堪比春运。 2月18日凌晨,故宫官网甚至被挤到瘫痪。 在二手交易平台上,故宫元宵夜活动门票已经被炒到了3000元。 2月19日凌晨故宫博物院官方微博公告称: 为保证故宫和观众的安全,并为预约成功的观众提供良好的参观体验,本次活动
腾讯文旅
2020/06/17
4720
数字周报87期 |​腾讯文旅发布六大维度升级;腾讯首次公布智慧农业战略
行业动态 腾讯文旅发布六大维度升级,全面助力行业迈向数实融合新阶段 11月30日,2022腾讯全球数字生态大会智慧文旅专场在线上圆满举行。此次专场上,腾讯文旅发布了六大维度的全面升级,涵盖技术、行业、场景、产业、服务、生态方向上的深化成果,以及文旅四大赛道的丰富实践和前沿解决方案。同时面向行业新要求新变化新趋势,推出了更加开放的生态圈和更具价值的“朋友圈共创计划”,通过提供一站式综合化的数字文旅服务体系,助力产业开新局,迈向数实融合新阶段。(相关链接:一图读懂 | 2022腾讯全球数字生态大会智慧文旅专场
腾讯文旅
2022/12/06
5020
数字周报87期 |​腾讯文旅发布六大维度升级;腾讯首次公布智慧农业战略
大咖论道 | 单霁翔:故宫从文创产品到上元之夜的经营之道
3月18日,故宫博物院院长单霁翔在石家庄大剧院发表题为《匠者仁心——让故宫文化遗产资源“活起来”》的公益讲座。 在近两个小时的演讲中,单霁翔从管理理念、文物修缮、扩大开放面积、升级服务设施、数字文创等多个方面对故宫进行讲解,通过诙谐幽默的语言分享了故宫博物院近年来的建设情况和发展历程。 为了让文化遗产融入现代生活,让文物“活”起来,故宫博物院近年来做了很多尝试,从文创产品到“上元之夜”,故宫一次次用创意给大众带来惊喜。 单霁翔表示,文化创意产品必须要结合人们生活,要有实用性,最好还有一点趣味性。 在他
腾讯文旅
2020/06/17
4820
1亿“90后”沉迷博物馆,传统文化IP如何实现产业裂变?
这几年,博物馆“变了”。 在以前,“博物馆”给我们的印象,是玻璃橱里的文物展品,写满故事的雕像和画廊里的抽象派画作,是久远的历史遗迹,曲高和寡的艺术殿堂。 而今日的博物馆,是孩子们吃着的故宫雪糕,年轻人玩着的敦煌飞天皮肤的《王者荣耀》,是爱美的女孩子涂的口红、抹的眼影。 围绕博物馆特色内容,把“文化”与“消费”链接起来,“文创产品”让博物馆焕发新活力,600岁的故宫可以和年轻人打成一片,乾隆皇帝成为了最佳带货王。 2019年实际购买过博物馆文创产品的消费者数量已近900万,相比2017年增长超3倍。
腾讯文旅
2020/06/17
1.1K0
看故宫,一个小程序就够了 | 案例上新
今年是紫禁城建成600年,故宫博物院成立95周年,故宫博物院联合新华社全媒编辑中心、腾讯共同推出小程序“数字故宫”。 (长按识别进入“数字故宫”小程序) 故宫有9000+间古建筑,总占地面积1120000+平方米,186万余件馆藏文物。 这么大面积,这么多馆藏,怎么样才能一口气看完? “数字故宫”小程序,通过全面整合故宫在线服务,能让大家第一时间掌握故宫最全最新资讯。海量故宫数字资源“一站抵达”,无需在网站、微博、App等故宫多个数字平台上跳转。 未来,用户将可以在小程序“一键查询”故宫所
腾讯文旅
2020/08/11
1.9K0
热点关注 | 疫情让3D博物馆火了,在家“云看展”有多爽?
博物馆和美术馆近年来已经成为文化产业的“领头羊”,但在疫情影响下,人们不得不待在家中,线下实体商业遭受重创,博物馆也是门庭清冷。好在国内外各大博物馆和美术馆相继推出线上虚拟博物馆,让消费者足不出户就可以欣赏中外瑰宝,感受千年历史文化,3D博物馆也终于迎来属于它的高光时刻。今天你“云看展”了吗?(文末附有各大线上博物馆二维码哦~) 众所周知,疫情所带来的蝴蝶效应造成国内文娱产业数亿损失:春节档影片撤档,演唱会停办,全国各大景区相继关停……为避免人群聚集感染,“家里蹲”首次成为值得表彰的、可以为社会做贡献
腾讯文旅
2020/06/17
9410
疫情期间 可以在家逛的500家博物馆
数据截止于2020-01-27 12:05 小伙伴们,这几天大家是否纷纷收到全国各地博物馆、美术馆、文化景点的闭馆通知?是的,由于病毒肺炎疫情严峻,故宫博物院、国家博物馆等全国性博物馆纷纷发布闭馆公告,避免人员聚集交叉感染,博物馆暂停开放。如国家博物馆表示闭馆后已办网上预约的将自动取消预约,不会影响个人信用记录。 各馆闭馆公告 各地博物馆纷纷闭馆,线上展览不打烊 各地博物馆新年的活动计划纷纷做了调整,原广东全省博物馆发起的“春节不打烊,博物馆里过大年”的活动,也暂停取消了线下博物馆的活动。但是
腾讯文旅
2020/06/17
5500
热点关注 | 全国150个线上博物馆汇总 在家足不出户“云看展”
假期一天天延长, 一天到晚除了吃就是睡, 是不是感到莫名的恐慌? 那是知识的空虚感在作怪, 快来云看展, 足不出户,看遍全国精品展览。 展览资源来源于国家文物局 1.贺岁迎祥——紫禁城里过大年 故宫博物院 https://www.dpm.org.cn/subject_hesui/guide.html 2.全景故宫 故宫博物院 http://webapp.vizen.cn/gugong_app_pc/index.html 3.归来——意大利返还中国流失文物展 中国国家博物馆 https://we
腾讯文旅
2020/06/17
21.6K0
故宫和腾讯,办了一个“没有文物”的文物展
我们把故宫搬到了深圳。 在这里,你可以欣赏超高精度数字国宝的精美纹样。 但现场,却 没 有 一 件 实 物。 眼前的一切,借由深度数字化而实现。 这场特别的展,就是—— “‘纹’以载道——故宫腾讯沉浸式数字体验展” 由故宫、腾讯联合主办。 室内装潢顶峰美誉的皇家建筑——倦勤斋,首次在深圳VR数字化呈现。 来到“锦绣世界”展区—— 你会看到一个高达8米的裸眼3D视觉装置。 空间内部借助腾讯多媒体实验室沉浸式渲染技术,实现文物22倍超清放大,让观众置身于纹样万花筒的世界中。 再来
腾讯文旅
2021/12/20
1.1K0
600 岁的故宫,也上了人工智能的车
中国的民族记忆——故宫,于明成祖永乐四年(1406年)开始建设,到永乐十八年(1420年)建成,到今年正好整整 600 年了。
HyperAI超神经
2020/07/14
1.1K0
“游云南”数字博物馆上线,“黑科技” 让文物活起来
5月18日国际博物馆日  文末揭晓13项博物馆“黑科技”哟~  ↓ 随着《国家宝藏》的热播 以及故宫文化资源走进平常人家 高冷的博物馆“热”起来 大英博物馆 台北故宫博物馆 故宫博物院… 你是否lost在硕大的博物馆之中 在万千藏品中不知道何去何从 △ 博物馆的秘密 逛了好半天 离开时才发现没见馆藏珍宝踪迹 仅疲于捕捉各个展品的文字介绍 想寻求“贴身”导游边走边讲的便捷… 传统的逛馆模式 已经out 博物馆 得智慧化 再智慧化一点! △ 智慧化博物馆 5月18国际博物馆日,云南省文物局和“游云
腾讯文旅
2020/06/17
1.1K0
大咖论道 | 单霁翔:文旅融合的故宫实践(视频)
单霁翔,文化和旅游部党组成员(副部长级)、故宫博物院院长。中国人民政治协商会议第十、十一、十二届全国委员会委员,首都规划建设委员会委员。单霁翔毕业于清华大学建筑学院城市规划与设计专业,师从两院院士吴良镛教授,曾获得工学博士学位,被聘为北大、清华等高等院校兼职教授、博士生导师,出版《文化遗产·思行文丛》等十余部专著,并发表百余篇学术论文。 来源:中国旅游报(zglybs) 我在故宫博物院(以下简称“故宫”)工作。过去,故宫有两个主管部门,一个是原文化部,一个是原国家旅游局,因为故宫不但是5A级旅游景
腾讯文旅
2020/06/17
4090
如何用多种方式实现文物的“活化”?
还记得2018年9月那场举世震惊的火灾吗? 2018年9月2日,素有“巴西故宫”之称的巴西国家博物馆意外发生火灾。 几小时之内,大火将这座具有200年历史的博物馆严重烧毁,2000万件藏品损失惨重。 火灾前的巴西国家博物馆英姿 据副馆长赛雷若(Cristiana Serejo)表示,大约只有10%的藏品幸免于难。 火灾重创后的巴西国家博物馆 巴西国家博物馆是拉丁美洲最大的自然历史博物馆,此次大火被称为是人类博物收藏史上的一次巨大灾难。火灾发生后,巴西一直呼吁“从灰烬中重建”博物馆,根据数字资
腾讯文旅
2020/06/17
1.2K0
多媒体实验室助力沉浸式数字故宫展
腾讯多媒体实验室 腾讯多媒体实验室,专注于多媒体技术领域的前沿技术探索、研发、应用和落地,包含音视频编解码、网络传输和实时通信,基于信号处理和深度学习的多媒体内容处理、分析、理解和质量评估,沉浸式媒体(VR、AR、点云等)系统设计和端到端解决方案;同时负责国际国内行业标准制定,包含多媒体数据压缩,网络传输协议,多媒体系统和开源平台等。 /  数 字 故 宫  / 12月18日,由故宫博物院和腾讯联合主办的“‘纹’以载道——故宫腾讯沉浸式数字体验展”在深圳海上世界文化艺术中心开幕。这是故宫超高精度“数字文物
腾讯多媒体实验室
2022/01/17
8450
作为文旅、文创产业的一环,博物馆研学旅行如何规范?
日前,国家文物局发布了一则题为《社会机构组织博物馆研学旅行应规范管理提升质量》的文章,文章表示:“对于部分社会机构、个人以‘博物馆游学’之名,开展粗放的、只游不学、走马观花、名不副实的‘研学旅行’活动,损害中小学生利益的行为,表示坚决反对。” 依托博物馆自身资源禀赋与文化价值,组织中小学生开展研学活动,是素质教育的一种有效探索与实践。在研学活动中,如何避免流于形式、华而不实,切实提升参与学生的学识素养,达到素质教育的目的,值得各方思考。 山东青岛重庆路第二小学的孩子们把暑期实践课堂开进了青岛贝壳博
腾讯文旅
2020/06/17
3850
大咖论道 | 段勇:博物馆数字化建设的9个热点问题
因为疫情,今年全球有85000家博物馆闭馆,此后云上博物馆、博物馆直播成为了公众与博物馆的交流方式之一,博物馆的数字化建设也被推到了台前。 虚拟博物馆在未来会如何发展?科技对博物馆文物保护管理以及公众体验带来何种帮助?如果万物终将消亡,博物馆将如何存续? ▲上海大学党委副书记、中国博物馆协会副理事长段勇 6月27日,《中国博物馆公开课》在上海刘海粟美术馆以直播的方式启动。上海大学作为《中国博物馆公开课》的主办方之一,上海大学党委副书记、中国博物馆协会副理事长段勇对“公开课”以及博物馆的数字化建设、
腾讯文旅
2020/07/07
9290
三亚将在全域旅游背景下建设国际旅游消费中心 | 每周文旅资讯精选
三亚将在全域旅游背景下 建设国际旅游消费中心 1月23日,2019年中国旅游产业发展年会在北京举行。三亚市市长阿东出席了该年会并做题为“在全域旅游背景下建设国际旅游消费中心的三亚行动”的演讲。阿东提到,为推动海南建设成为具有世界影响力的国际旅游消费中心,具体举措包括以下几点: 一、拓展旅游消费发展空间,构建丰富多彩的旅游消费新业态,全面提升旅游消费供给质量 二、提升旅游消费服务质量,创建国际一流的旅游消费环境 三、对接国际化消费理念和消费模式,提升旅游消费要素的国际化水平,建设世界知名的旅游消费目
腾讯文旅
2020/06/17
4440
舒展:腾讯愿做博物馆的数字化助手,让文博IP活起来
互联网企业应扮演好数字化助手的角色,助力传统产业转型升级,让文博IP活起来,让更多的人走近文博。 ——腾讯文旅业务总经理  舒展 舒展,博士,腾讯文旅业务总经理,云南腾云信息产业有限公司总裁,腾讯研究院互联网+创新中心副秘书长。 目前负责腾讯公司智慧文旅业务,多年来致力于互联网+政务服务、智慧旅游和数字经济领域的研究与实践;目前同时担任“一部手机游云南”项目负责人,这是由腾讯和云南人民省政府共同打造的中国第一个省级全域智慧文旅产业平台项目。 在近日举办的“技术创新与博物馆发展高峰论坛”上,腾讯
腾讯文旅
2020/06/17
1.8K0
推荐阅读
相关推荐
腾讯携手九大博物馆,用青年创意活化传统文化
更多 >
LV.0
这个人很懒,什么都没有留下~
加入讨论
的问答专区 >
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    本文部分代码块支持一键运行,欢迎体验
    本文部分代码块支持一键运行,欢迎体验