Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
•
IT NachrichtenSamsung Galaxy S26 FE review: false economy(24.09.2026 um 22:07 Uhr)
••••••••••
IT NachrichtenSamsung Galaxy S26 FE review: false economy(24.09.2026 um 22:07 Uhr)
•••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Let's build a test runner in Elixir.

I have not interacted much with metaprogramming in Elixir, but I thought it was time to check it out. For this article, we will be building a test runner to explore the topic. So let's begin. Quote First, we need to…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

I have not interacted much with metaprogramming in Elixir, but I thought it was time to check it out.



For this article, we will be building a test runner to explore the topic.



So let's begin.






Quote



First, we need to understand how ExUnit tests are internally represented on the AST.



We use Elixir's quote and unquote methods. Quote allows you to get an internal representation of your expression as the AST. For intance:




iex> quote do: 1+2

{:+, [context: Elixir, imports: [{1, Kernel}, {2, Kernel}]], [1, 2]}







  • The first element, :+, is an atom denoting the function call, or another tuple, representing a nested node in the AST.

  • The second element, [context: Elixir, imports: [{1, Kernel}, {2, Kernel}]], represents metadata about the expression

  • The third element is a list of arguments for the function call.



Interestingly, this AST representation {:+, [context: Elixir, imports: [{1, Kernel}, {2, Kernel}]], [1, 2]} is exactly what the compiler sees. Other languages, such as flavours of Lisp will have you write the source directly as an AST. Elixir however allows you to convert from high-level source to low-level AST with a simple quote invocation.



Next thing we need to understand about ASTs in Elixir is that some literals will have the same representation as high-level literals. Namely, atoms, numbers, strings, lists and tuples




iex> quote do: :atom #atoms
:atom
iex> quote do: 123 #numbers
123
iex> quote do: 3.14 #numbers
3.14
iex> quote do: [1, 2, 3] #lists
[1, 2, 3]
iex> quote do: "string" #strings
"string"
iex> quote do: {:ok, 1} #tuples
{:ok, 1}
iex> quote do: {:ok, [1, 2, 3]} #tuples
{:ok, [1, 2, 3]}









Unquote



The unquote Macro allows values to be injected into the AST that is being defined. These allows the outside bound variables, epxressions and blocks to be injected into the AST. For instance:




iex> number = 5
iex> ast = quote do
...> number * 10
...> end
{:*, [context: Elixir, import: Kernel], [{:number, [], Elixir}, 10]}






Here we see that the value of number was not injected into the AST. We instead got a local reference for number provided. However,




iex> number = 5
iex> ast = quote do
...> unquote(number) * 10
...> end
{:*, [context: Elixir, import: Kernel], [5, 10]}






Here because we called unquote(number) the value 5 gets injected into the AST.



Quote and Unquote allow you to build ASTs without fumbling with the AST by hand.






Building the test runner.



Now let's start from the bottom. What is the basic building block of a test? I think it is the assert call. This checks the equivalency of your function being tested and the expected result.




assert MyFun.sum(2,5) == 7






I think we should build an implementation of this function.



Side Note: I am a big proponent for TDD, so we start by builidng specs for our assert function.



We define a function assert/1 which takes one argument that must be an expression checking equivalency.




iex> Tahinix.assert(2 == 2)
:ok






In our tests, we check the assert cases where we use :!= and :==




defmodule TahinixTest do
require Tahinix

use ExUnit.Case

test "assert returns non-zero value when tests fail equivalence" do
assert Tahinix.assert(2 == 4) == {:error, "Assertion failed: 2 == 4"}
end

test "assert returns non-zero value when tests fail non-equivalence" do
assert Tahinix.assert(2 != 2) == {:error, "Assertion failed: 2 != 2"}
end

test "assert returns 0 when tests pass equivalence" do
assert Tahinix.assert(2 != 4) == :ok
end

test "assert returns 0 when tests pass non-equivalence" do
assert Tahinix.assert(2 == 2) == :ok
end
end






Now to make our tests pass, we start by implementing the code for :== function first. We use pattern matching in the atributes in order to get our left hand side(lhs) and right hand side(rhs)




  #{:==, _, [lhs, rhs]}
defmacro assert({:==, _, [lhs, rhs]}) do
quote do
lhs = unquote(lhs)
rhs = unquote(rhs)
result = lhs == rhs

case result do
false -> {:error, "Assertion failed: #{lhs} == #{rhs}"}
_ -> :ok
end
end
end






This will match expressions of the form lhs == rhs, eg: 2 == 2. By having lhs and rhs in the head, we bind these values to the variable names, which we then pass to unquote.



We then use case to get our return value. If the result if false, we return a tuple {:error, reason_for_error}. Otherwise we retun :ok



Similarly for the assert :!= we pattern match in the function head to get




#{:!=, _, [lhs, rhs]}
defmacro assert({:!=, _, [lhs, rhs]}) do
quote do
lhs = unquote(lhs)
rhs = unquote(rhs)
result = lhs != rhs

case result do
false -> {:error, "Assertion failed: #{lhs} != #{rhs}"}
_ -> :ok
end
end
end






When we run our tests, we find that they are all green now.





The question now remains, how complex an expression could we include in the left hand side? Let us right some tests and see what we can add in.




test "Assert returns :ok when tests pass with a complex lhs function" do
assert Tahinix.assert(5 * 2 == 10) == :ok
end

test "Assert returns :error when tests fail with complex lhs" do
assert Tahinix.assert(5 * 2 == 12) == {:error, "Assertion failed: 5 * 2 == 12"}
end






Finally, can we test assertions on the output of functions? For instance,




    iex> Tahinix.assert(MyMod.say_hello == :hello)
:ok









Test blocks



The next thing we would need to figure out is how to implement a test block in our runner. Let us inspect the AST that ExUnit test case returns when we pass it into quote.




iex> test_ast = quote do
test "sum test" do
assert 2 + 2 == 4
end
end

{:test, [],
[
"sum test",
[
do: {:assert, [],
[
{:==, [context: Elixir, imports: [{2, Kernel}]],
[
{:+, [context: Elixir, imports: [{1, Kernel}, {2, Kernel}]], [2, 4]},
6
]}
]}
]
]}






Remember the breakdown we did earlier on the parts of the return value of quote?



Here we see that the function name is test with no metadata(2nd arg), and its arguments are a do block. All we need to do for our test runner is build a function that returns a similar AST.



We start by writing our tests as usual:




describe "testing test blocks." do
test "A test block of a passing equivalence test" do
assert Tahinix.test("equivalence of 2 and 2", do: Tahinix.assert(2 == 2)) ==
{:ok, ["equivalence of 2 and 2"]}
end

test "A test block of a passing non-equivalence test" do
assert Tahinix.test("non-equivalence of 2 and 2", do: Tahinix.assert(2 != 2)) ==
{:error, ["non-equivalence of 2 and 2", "Assertion failed: 2 != 2"]}
end

test "A test block of a failing equivalence test" do
assert Tahinix.test("equivalence of 2 and 3", do: Tahinix.assert(2 == 3)) ==
{:error, ["equivalence of 2 and 3", "Assertion failed: 2 == 3"]}
end

test "A test block of a failing non-equivalence test" do
assert Tahinix.test("non-equivalence of 2 and 3", do: Tahinix.assert(2 != 3)) ==
{:ok, ["non-equivalence of 2 and 3"]}
end
end






Now we need an implementation to make the tests green.




defmacro test(test_name, do: block) do
quote do
test_name = unquote(test_name)
block = unquote(block)

case block do
:ok -> {:ok, [test_name]}
{:error, reason} -> {:error, [test_name, reason]}
end
end
end






This function will execute whatever is passed in the do block (in our case we will assume it is an assertion). If the block runs without error, we return an {:ok, [test_name]} tuple. Otherwise, we return a {:error, [test_name, reason]} tuple.



And now we can write blocks of code like




Tahinix.test "Adding 2 numbers" do
Tahinix.assert(MyFun.sum(2,1) == 3)
end






And it will return whether or not our test has passed.






Conclusion



With all this, we already have a sample test runner with the basics working. Further improvements might be adding in describe blocks, accumulating the test that pass and fail and showing them to the users, and adding in other important functions like refute



And now we also have a better understanding of how metaprogramming in Elixir can help us write utilities like test runners. But remember, the first rule of writing Macros, Don't write Macros



The full repo can be found here



References:




  1. Metaprogramming in Elixir by Chris McCord

SOC Incident Playbook: Remote Code Execution (RCE) Defense
1 Warnungen
title: Detect Exploitation - Let's build a test runner in Elixir.
id: f1045ce4-a573-4767-b88a-172ac329bb4e
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-24
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-24"
        description = "YARA Signature for "
    strings:
        $str = "Let\'s build a test runner in E" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("Lets build a test runner in Elixir")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*Lets build a test runner in Elixir*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "Lets build a test runner in Elixir"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc
🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich Let's build a test runner in Elixir..... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Let's build a test runner in Elixir.

Thematisch verwandte Begriffe: Lets, build, test, runner · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-81473 | Dell Rugged Control Center (RCC), versions prior to 5.2.206, contain an …
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel • Rechts: nächster Artikel • unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...
↗ Original-Quelle