From 8246feecc69eecfad84bc204316751a9408930e0 Mon Sep 17 00:00:00 2001 From: Andrey Marchenko Date: Mon, 5 Jan 2026 17:14:50 +0100 Subject: [PATCH 1/2] add an ability to override runtime tags to reuse Datadog's code coverage data to skip test files in local environment --- README.md | 74 ++++++-- docs/images/runtime-tags-datadog.png | Bin 0 -> 50527 bytes internal/cmd/cmd.go | 5 + internal/runner/dd_test_optimization.go | 17 +- internal/runner/dd_test_optimization_test.go | 174 +++++++++++++++++++ internal/settings/settings.go | 23 +++ internal/settings/settings_test.go | 89 ++++++++++ 7 files changed, 369 insertions(+), 13 deletions(-) create mode 100644 docs/images/runtime-tags-datadog.png diff --git a/README.md b/README.md index e862c83..8ac1ad1 100644 --- a/README.md +++ b/README.md @@ -142,16 +142,17 @@ In CI‑node mode, DDTest also fans out across local CPUs on that node and furth ### Settings (flags and environment variables) -| CLI flag | Environment variable | Default | What it does | -| ------------------- | --------------------------------------------- | ---------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `--platform` | `DD_TEST_OPTIMIZATION_RUNNER_PLATFORM` | `ruby` | Language/platform (currently supported values: `ruby`). | -| `--framework` | `DD_TEST_OPTIMIZATION_RUNNER_FRAMEWORK` | `rspec` | Test framework (currently supported values: `rspec`, `minitest`). | -| `--min-parallelism` | `DD_TEST_OPTIMIZATION_RUNNER_MIN_PARALLELISM` | vCPU count | Minimum workers to use for the split. | -| `--max-parallelism` | `DD_TEST_OPTIMIZATION_RUNNER_MAX_PARALLELISM` | vCPU count | Maximum workers to use for the split. | -| | `DD_TEST_OPTIMIZATION_RUNNER_CI_NODE` | `-1` (off) | Restrict this run to the slice assigned to node **N** (0‑indexed). Also parallelizes within the node across its CPUs. | -| `--worker-env` | `DD_TEST_OPTIMIZATION_RUNNER_WORKER_ENV` | `""` | Template env vars per local worker (e.g., isolate DBs): `--worker-env "DATABASE_NAME_TEST=app_test{{nodeIndex}}"`. | -| `--command` | `DD_TEST_OPTIMIZATION_RUNNER_COMMAND` | `""` | Override the default test command used by the framework. When provided, takes precedence over auto-detection (e.g., `--command "bundle exec custom-rspec"`). | -| `--tests-location` | `DD_TEST_OPTIMIZATION_RUNNER_TESTS_LOCATION` | `""` | Custom glob pattern to discover test files (e.g., `--tests-location "custom/spec/**/*_spec.rb"`). Defaults to `spec/**/*_spec.rb` for RSpec, `test/**/*_test.rb` for Minitest. | +| CLI flag | Environment variable | Default | What it does | +| ------------------- | --------------------------------------------- | ---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--platform` | `DD_TEST_OPTIMIZATION_RUNNER_PLATFORM` | `ruby` | Language/platform (currently supported values: `ruby`). | +| `--framework` | `DD_TEST_OPTIMIZATION_RUNNER_FRAMEWORK` | `rspec` | Test framework (currently supported values: `rspec`, `minitest`). | +| `--min-parallelism` | `DD_TEST_OPTIMIZATION_RUNNER_MIN_PARALLELISM` | vCPU count | Minimum workers to use for the split. | +| `--max-parallelism` | `DD_TEST_OPTIMIZATION_RUNNER_MAX_PARALLELISM` | vCPU count | Maximum workers to use for the split. | +| | `DD_TEST_OPTIMIZATION_RUNNER_CI_NODE` | `-1` (off) | Restrict this run to the slice assigned to node **N** (0‑indexed). Also parallelizes within the node across its CPUs. | +| `--worker-env` | `DD_TEST_OPTIMIZATION_RUNNER_WORKER_ENV` | `""` | Template env vars per local worker (e.g., isolate DBs): `--worker-env "DATABASE_NAME_TEST=app_test{{nodeIndex}}"`. | +| `--command` | `DD_TEST_OPTIMIZATION_RUNNER_COMMAND` | `""` | Override the default test command used by the framework. When provided, takes precedence over auto-detection (e.g., `--command "bundle exec custom-rspec"`). | +| `--tests-location` | `DD_TEST_OPTIMIZATION_RUNNER_TESTS_LOCATION` | `""` | Custom glob pattern to discover test files (e.g., `--tests-location "custom/spec/**/*_spec.rb"`). Defaults to `spec/**/*_spec.rb` for RSpec, `test/**/*_test.rb` for Minitest. | +| `--runtime-tags` | `DD_TEST_OPTIMIZATION_RUNNER_RUNTIME_TAGS` | `""` | JSON string to override runtime tags used to fetch skippable tests. Useful for local development on a different OS than CI (e.g., `--runtime-tags '{"os.platform":"linux","runtime.version":"3.2.0"}'`). | #### Note about the `--command` flag @@ -463,6 +464,59 @@ Rake::TestTask.new(:test) do |test| end ``` +## Running tests locally with CI's skippable tests + +When using Test Impact Analysis, skippable tests are scoped by runtime environment (OS, architecture, language version). This means tests skipped in your Linux CI won't automatically be skipped on your macOS development machine because the runtime tags differ. + +The `--runtime-tags` option lets you override your local runtime tags to match your CI environment, enabling you to benefit from Test Impact Analysis locally without re-running your entire test suite. + +### How to use + +1. **Find your CI's runtime tags in Datadog** + + Open any test run from your CI in [Datadog Test Optimization](https://app.datadoghq.com/ci/test-runs). In the test details panel, look for the `os` and `runtime` sections: + + ![Runtime tags in Datadog](docs/images/runtime-tags-datadog.png) + + Note the following tags: + + - `os.architecture` (e.g., `x86_64`) + - `os.platform` (e.g., `linux`) + - `os.version` (e.g., `6.8.0-aws`) + - `runtime.name` (e.g., `ruby`) + - `runtime.version` (e.g., `3.3.0`) + +2. **Create the runtime tags JSON** + + Build a JSON object with these tags: + + ```json + { + "os.architecture": "x86_64", + "os.platform": "linux", + "os.version": "6.8.0-aws", + "runtime.name": "ruby", + "runtime.version": "3.3.0" + } + ``` + +3. **Run ddtest with the override** + + Pass the JSON as a single-line string: + + ```bash + ddtest run --runtime-tags '{"os.architecture":"x86_64","os.platform":"linux","os.version":"6.8.0-aws","runtime.name":"ruby","runtime.version":"3.3.0"}' + ``` + + Or use an environment variable (useful for shell aliases): + + ```bash + export DD_TEST_OPTIMIZATION_RUNNER_RUNTIME_TAGS='{"os.architecture":"x86_64","os.platform":"linux","os.version":"6.8.0-aws","runtime.name":"ruby","runtime.version":"3.3.0"}' + ddtest run + ``` + +> **Note:** Test Impact Analysis works on committed changes. Make sure to commit your changes before running ddtest to see accurate skippable tests. + ## Development ### Prerequisites diff --git a/docs/images/runtime-tags-datadog.png b/docs/images/runtime-tags-datadog.png new file mode 100644 index 0000000000000000000000000000000000000000..df7d4b416c2e052fd8065fb76a411597f985b2de GIT binary patch literal 50527 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̫&XmN5-b}QZ%JFo3Iur}yiXCQ=3clHR$Vh0{E^T~ImMeu(BwRW*AXVkG7c z|HA3xs${e-qKs0~r^NDX@xHQR1nssS4c_+ehpk!FXNxJLH=(l4A~c}jlYC}Uh0eUH zlid`s0>E5UHeCM?*I;CpG(7G*GAYqS$iIGLAmLpCtYUNuk-I*53-JW_Bcc2QWAk#q zNB{iXMkI@Z3#4uRqX|s@&W-Z{7pwoVRjsF3Lf2dVCjtTj^6now^WN=JsZ{qEMvl_# z{8?|S3)V6Mx?Q@^m#WXlMHjg_!zMTK<<%uWcdAjs&t5#{>XpcKa)^Mxn=*7dvkk3wda|i z#oPe^fdshdd+A@BKWEFzD*ctDl6?@!?M2Z0uc)^O1TAkG3ZKhUokP6P{^d8}SD)v{j{x@5A&f@id z44LRZ@?+F8nCF>l1eicTn&DaQOUnz6eEsu>*KoSz#YCZu;9M)j^NFeX;D(LD3DBCO zd$fm(<*^_*ZT~DQOF@yt@XT1x;pc$nd?0r;yVE)Zd3TsIVD@8G1RtgDnBHuYwgMWi zX^qL#+YbMg@E6hY0@R4qkf~a(E-&2mITK(r<>&8k41xU5HX!%cpziF7)d`blF;S=M z$==`Bms4?WEf@S8X8_>=1AeyE_k{3X-r}69PN~+QlQm?=_3h^%XKwDl&7VO)sI^F{ z4L2b&#iWJBOQunYxYEeg4G?J$LN zB_miOUavtJ%CxkTPS5TLAg_WF$jkxyI&kc`)3+ZZI0BMT<$`uk^}7sC%?_s!=)Oh2 za7al>F(Z@7u{GIy5B>Bz#LEbPqZJgoWakA5sy5bj#^E0XuD*Y=B)kPP;h9dsk`|?G zKNBJ;c>nwFe)#KcNFQeo3+GTkqDk}n&kgZ42H35H9Ehdq|5i|4XS1i=^&fkTbme6~_Ns6vN3YjG2V~^E zpRnIGi#1B3c-S{;z@(n+RHK=)C zpv!AzIA24^U^MH!+tC@%=xfgPd^H2`)pqKp6#KlHK|uLW!eyNJFSi5e z7?1xAaD+pCLC>Ea;w8ynqf&L?`?Q}a%!5zL0kB^M*mNbW<$wMxazy{GC}(oD>iYB< z2-u&fTC>l~hEKJTYY6@ythez6OUhxl)o45&{`3W$+JuQq{WPk-K+4^J>Y&RI1-B8O z{zhovCIdV-8k_wv_ESeGYx}o@)&%ySDGL++pP~Xl2aV>7h5IyI0;&NZA0Z6r-L0|J zhoPS;>>S)Wz26fyLSt7vPY@RVP7rE<1b35Q9{i^V90oVZ3K#nSR2JN+4dAZ=xP%-K zF)E%PP!HTw@dk%0d7=K#&ON~YhfnAaxMUdSeYHv{fhFO^|= zOOP$Oh=Te;M;|BG`~3LoNThNYTQPVye{+7CD`r&|?PJ97t0Y)YrjsuG`h6naMpi~e zG>@z&YNFIDx=!9q<4^6;1p)#lcZ(CAf*l!QYVdI}5SG-rS;AFNbhUTY(60^+`5t2vP;it?WdhAHLqUzV*qHt8jVLWF_B}%wa4Y zr_S=3+q`bXL#4@&E13^PKFcdgJ+(|mA3?U!g-_6DSY4Vb)do2fvAR~&#pic);rx>N z`t^Q|nwsV7HIO3}6E9Nu6Jl-xxjP~l%cSA!ulJ>4XyS1Y41UH98xS2o$ ztRYQ}CLnH3zpM1=;wXNdrOx33RfwjMO$IAPzqLbq-djCquz9CSPRX$=xfB@NRlgwD z(Wivy5`OU0?Z>73`T2NLRLnN{yKhZ)5u&c9`x=h}5%^YZqybs^7-l%%#ebfh5;2_L zT+Z#Z0a1pHJ{f4NaC~;^(@8bItv(^YoU38OU?#=BG!w`E+U7)zeK{C`t)a{>>VqrA zL}=gO+{f*>-WF0}?vln=rH;G0lV@}@X<#&tWSWnReq-unLshC-!E$F|{2I5=R!7tI zu-M)M$=%f1fZ;yofY}t?7q7OA34Z_Q`tZ1SJvtJc2~Ly4)mO6LtUAoDZ7)f-Hd=TW zj%Y&R>dfsLC|nzzuLtbtoerI2S!0Wp`R-`_*5!rI*ZD5vBZHaYBG1sNvyb%dzxnm{ z2A_W=><-3t`H*C>OUCPeO8KKmy*|0=1_V88M=S1{1k<2bOUGE!4Pu#BVem5~mE>TF+VHoia zI@t#VA+?*7=e)cn`X*+hGv;?>!nnsAH_O&lfeXe&MJn|5EH( zlcef7eJOHkbX|dJ41!ozO3D>FfgAfw))hV5{%t0=)Tt|L>=qNyo^WzWQ zjq<>(Zc)B-xm+`LT$y0n?v4q_gW=6Gqm>Op67YkF4 z;ec+iMVy-=Bz(fe+rDc&QLbu1CFiZF*gYGmbl)!+R!Lc5sce!j7mlu~$$||BiLZ*aO1Q-Vz%)H4z7XL=eWi6^ztjh8~bhc{z*8C7mS!dmF zFB*xe^0mIVjm9-v+jxUBO@WTX+<~U|n+eZ`VE)d%hV7Wq(n0ve{aPWs^=VKcZj&Uc zHj%;2#r?X~h2Q<5bMv`--NkON@~275lC+DIkGA3Q#ZiReM|yCm)r| z@5A?j9a9@ao7}833E4GVEukIH{F2Hp0CQRf8}+t8scB3j=gkschMz*p?1;ag)4FP! z3CsO>KrBmK)hrbD0pbvZ_Fs5oK2Et6Lx} zqcP<4VY^-m^8+x);Y9t{dxl>ZmMou*aQRNuX0}~BDD6I6mg!3EIBhK=ccQ$f5#_Mv zr_oXg!}m5XwK1z5mk!=f(xUIEVz=lznr`Cvfh5cv*I#OL*ea}qHJ%6cF(J|$``0XD z5AJ`fy%yX~Ai3R$Jw?~7Z_}70FUe>P@KP>NanM~EP~h>06K5-?5R21!{DU`{f!0WC z@;S-j(X4DSD@Dd2q5#Otlf?=wOD;ebZz?Rmi%B)CeCw+6yM z+~IL`rgvQO!P-tzGeR01UZp`k2t$6f*Mx+=LWN=m-nGl^{Y*^L!vILU37z+pMEvE^ zk!Li@s(q1t8Pl8fnwN5RIy%HBT`cWi`K46*ZWj3W2Ny)S9!|NG8YGJGtnVk>=!_ld zW-GH2itBxoe?7e1I9w1ccTMJ7>k3Urr?pLJGGSp0&ZfCva~LmoVp?m`NKv;Qvr!mZ z8)By!V}xt%ri5XrIuhXX2PV(-{_X|jsXP;k8R2p!-u>3e4cQWnyhvLbrIpiHWsU0g zn6~=RI+Na%v&%n3wM@$iPb7|T#jR2>`5v7dD+nRu_AqCgG%Du(Q$CfEc*$9Y3;$$ z(%QFmbh@lHP#xF7&R?vR{c7ICF{EtwwYXH$NL+cXLNZ1k-9h9UUEvu?dGIj|Q z685ho z^WQJ9EZDh@5@k3unU})X;)$8W=zEAKVe-PuwJ}VV3^1_dSdR!~(n?BxrcNSj6f+`Iyx1LCP=^3V680ete?vmiz)x=Cq4b)s57kL{ zn>vG@+teZ|_ke&uw7@syQadpG?YjPc{csX%}%)`pIkI&OD%B&y%boibi?;-cn?6};bZ(u*#UxVBDqf&Ljm+m7sJQ2^@` z-zR8$h){w%&Ja*n6_jDZ>s76z9dZ8HUd4?Mn-=p5PFg=y=(3dLzFa|e+D*v1r))0M z0jrj+nKuseV0*>sFkQ}QYUE9+uA2+!qZT~!5p%-h9LlxkTzb&ZqgUT(LXK@XxNX>j zrZlz&g^krc)iR=vof4XC_a5qb{=-M3)g0I)I5tTOj7%j>$+bN8g4@w zL|{T8S#7(Cf$nQIp+0+I^=jCUt%?MT^@saiNk%(|)8*(5JoXN0X=SocdhHp8RQ@b)zjJiBB~+n&f8a36ng4Kf zC$6;V1PUrVZzoNFr}D7EYdV^qZE~@<86>n*!Wq5theNXju^jfUS}&$Q6c<~`qVG_2 z)WBL~md=Y~&vw3VwQim4_rx!W6x64g5_rKSz=@jNB}#$gBX1k~eUsDF-Kot2JBfPhIVYVoUxqIiJ`XMjt}dK6=WtqEfrDvfWr2Fm=VXP7 zjg!~Ng-X@Q4}eDZywy!_G)?+e06LnsR*piu-o+#&!=t%3qo@g_7TpFmn`uIRz(YyX z^$}yRg81=s9qg$YriOHoMw?F#6cSw|8;&qlY4!xw#ZhN9#Oh4X}mx3v!yX?T?2i;h#=AK2W0f zIU861Ayi`aThG87FXvl@<7%_^MV})*nbE_U){m%9{Z&?4F>_U2I&UwC78Y@TaBPW! zjdwO32Nkx{go&N|$J!SAa!8|$37e~Y%MgL`-1+hL#dY#(JKefo>#3PpQKaX4t}P;g z{zFve0USr2&M_w~rT*VaH_b-3z1P}E{jCj((ypB8iTTmH};2ogex=Mu?+bcZc%)xniLm zDXQG~GBEkO+Q4x#AgiQmr?BH%M|icNS$UQQhtBo%TA?%MmNr8?yuOFM^s@3Od6x&s zLv^=?Mu&`mSF?HmV#XFvXC!+T!4g zaj`xx{C0NmT!JJty-X*Ki8r*v-=K9Pwm(rawt<>NFXQghbU)U5SWM#E>n)lnzjtsr zA4YvVXU=9bt@UuU{&L`k!P4j5a@JXYB&tFf2G!xUm#s=@;@SJ&3Q#&M0^ z@dxe1YG4d|Gb`hH0@IS661<0X=_O>GeayVO)NQL(Qn;be+tOCQ8&%CPi9Yyob<%vPi>85VuTp*ngIs3=rc#msB3MbzXssrAmQkC)U-- zhT{nWnuV|>7Uu){b{}okTIwOX&c#SLwcq|r#7Q;%hd$-s>aOKALBXT_J5z807>y*)xX)-;W>CZ z1}+Clcy0NeD02>{!~O}MCio73dhb40$a9wpM)@K@y0Ll2JvVk{nm;D_t(DQ8@V1JOnvDM=F=~t^X47y$*RVY?#D1WOufK*>cirr;m?DkJaM_j*Emg16S`64}U`Iru%JHxRw|dzNGV7#;$G zu}t_YkF4DJ-?Xkjf#4=O2gAUIE`K+o#Ix*ETs6|Up!*JYqM_AuxHe;UM4Au+fGkI* zAM$1`78*$vLdk#`fj7ayBK2!X;BP1L)HF6^-umno;VFDIGpQZDOEt~3y7Ed*t4kcydi?lM|t%hn<)ZkAVX!KH+E z7E{;%mfB=(KD1R;A3%WfMnuGd(~CYdv&C%QHFbssTuLe_kp%GzOe@|hL!bULkOFvV z9Kkyr?6d(DtL-@@t`Xeo`Q>Coj=Z6VJqA{z)4cHvl~_kh$IGkT9SUIAO+HJdAA@7s z+tFs#WBu2)vWkx5X`W~_VNAAirDB8YP8dB~v7u%XZq2#%eCkctM`pJ>X^4!y=NP8@ z5^Fa(m=1dVizG1>mh1QFTjJ@lO@nC?TN~0LNa3_TK^xu&wxp*nNOFp+NXn569POXHsV4)-E3L0*QGEI(RFCCd* zTk55?H&iB8zP*5wquKh>!&LU``k}!ucDT#xk{O04F)y?K+SSh=tv}gy88G{n7nb@u7|a47%?qvD3;wN}yn+rNC=;s|+WsF8!7DrT*~Mz2?O0Tcr-OZRoO7RSD({PKZ+ec{rMVW%k=j}@G`j(0$mXh4rHp*h^C1F>)w9;+&Rf0be7Eow ze29!zpOE8&?}K%`qN6rj*1lS3JTkg{{~xiS9|_^;$Sc1N1K{VDzu}C}uF%2}Ho@@j z6l(qW!>xjg5i&pznMNV8BV-b(y`Ce{1zEe^?V0>$gLlvN(Wy$8V{rqt(?}FdQiAXY zVK0rsQ7Q{b$l*GyQke2<$bM3KAk%i8-J@%Us$u>u2^Z0;5C4W7PGp$G`2W?`RmMg6 zHBnTM1q2r9kdTyCQko?d>F(~9?hvFwq@+P4q#J4JQaTowMmnXN_gUiq>GijtWcP`= z_s*Sj=FHrmxi%*ZY%tK9#!t=k2WpsTuik$7Zkx!c1_8Sq^;n)EDuWg{ZFveRC=mbg zBjY*C;5__{%9yA2f}QQRC!U#7FrKpreV!xd6IK0=af3GBi?fl6Clr$WD;q^LTSH&b z1D7Rqx-0|!d8n(`j`En;{(0W_Z`_Sr{(y+$6+Ei&RfTUBNHCzcQ^5N06C_2D!F-$5 z;O5wqT@e~tXNI+&YHhVC#GOJ5%1YLG(qDgy_)l@$#TT6dQ{B+_-#2p5N~F=|toNHu z#6LdRDzdS^jXrjZ{`(dV7ilLJG-Ho~%Y8v2y9hgH~uty4}BP zV8^2gHN`8NZ+LkAT-Oh-TVM~|1X3@+z^3|{g%QLF>BnwMWo>sGEoSvD-s4W_@N&h{ zBYQ(%DIJm->`v8C+$IvE8cZpdH5N1nfVfj8!B=45cV-YS z$utbLCbYz1dhuFxwB?7`ZC8VgXm|umg@%@gQ^<&rxYGAx;gf;@-Up7CHOBrtKlDrIy_!9vp{6W4hTqUtL_~hoiY5-*>~jw z6?uh*hZfkJ92muDpBEJ2cZ~md=6WE|kS)O3IEe4_2TbAA=X}!1ANJMFvT?^8k72~W z)JYb0emjpE9d5-6kzGx1PyV5Bcx1zzJeW2fy59?JW3MpGltd9%n*QwG}if&O#QkUU&lbaU( zwr3LGL|0ILlj|bFV!Qhop6;nuO|v#MBJD?AR2Wc+C}6v|nfrpKvoj;V_aC{o?S_!R zZ7_WdFcZ38oNH>g_XDKY8El&<`K4wZw_;1zDwKJ8HR7!oBtbgj76}F}0R)>@xWUv# z+Q6`t-MCK_nXAZ$Mp2_V7TJE_Z@Krr&zAUJAA?qNdnA8jBsT*AkB>$f@@(-n4> zd3J!WISNF*gOw;|)$BAnIRfvsYw9FheHvGJU(A;~Rm&C~6$8}056fChn_N|)9ufVJSiw`ib3Af{jurnuL)`1;-; z(iU;aW|UB@dq_eK2QTU!;%3@M;u`6VFFx&{JKaW^z!RPL`I<->`Um4$1w@9}1Yh4_ zYv5%IrG?mp0nx)Z4Z8M0W3Ix%Xt0CO{+&hj?BVZL7-(*SB=&?If|q`@N_zK#P+;}Z z39)4o;y@le53{&8hR=-`!()JgLL$|^5nm<0$V0hOSy16k&7lIJSZoh<_HrUisQb?{!T z8GsK*wtY}Is&>6(5VF0!ZxZ7T;KeJ_OL+v$-Xk)c$G0ao0T%|m>Nk3t16u^5%)#bI z2N^w2=@{vAd9TF?*O9LZ1VS5)n5nz_L6($0CZjr7OTSwco9HzZz)d{ zvnuw-f{=M$tRaV6hXw{A-QKM>9Wutzl~Dd~Bbyrlv=DzNj|_(^ty89~X2*F#F)) zr&;iXc4Ge}w6q}hvxc}r)X_ij>%SsgKJ-!J3f0{MQc?V3-o^MWb%++0mLY;9&ndUA z>%Zsce{DR+klQNLc>7K=qjD4{NO*K~6jZeS(qg>fwODXey_~i-Q%o`kw%hZm75OC$ zGJ$t)$opa*U3Ap16E&!*^Cd7;m1${{&Nn`-okXy>O~*+P0f89@2zosLoD{*Hj}Mrx z99mpr7H%p+IaoF4>|n zs|&myO=t^*_s@_qMg(VcWQko8zEipV22=*JvRhJ@mR>d8DIK<7m}*I$P^o-Jf~A|@Y2&Mu?)rNug^Uu}gT?_fa` z8G=^UbF@GzK8-6BZZN=e=Lr6@Yi#{O9|p)?eXBsDmu$TnUWdh8z^c?2gs7}`*&9G! zQnnpk2CV^r3{Y=ed_oHEOVbb!Vz9JIl=i9FVaatM)!GCEDpqJ*IKyI5EYx`Mzx+5L z^j`wWWdLCQ3($j9mWL9#pL~cQaRSXUPRg!?dh?ot5pr?PMJds1dZ*E8<@F;9PT$YK|f zGLzOtJ(f!(qh{G3$%R*k=HJ491p}^9Ez(zo2)?%Xn=JzLTK)Us^aWhAA*H1GzHhDN zxFTDqWnkvao^~28+X&5Rtd9S|7*rs;B?&NzAx{k`%}cI_1i_H2f%A*mw7tFod`;rw1eq`QhlpZhhd~|`s zqj)LK2 zU|hSweF_8DwSw7bniLoX4t(cUiSz-vqWZ|~QbzPCjED(9t~Jm4{gYHDh-Tb7F=J)% ziy0R#0tmF(hEyma+?HiSKRKt5wlY_uc#~;fv!?%7Sw zV}2l6w>wpVF5EubIPl9%qGsBa$*F%`l1ZaouKs}2aZ6cMF3N^e->0j1y3*m8G;1=x ztjuw}{oh_1Qg&Ry1mS>^QT?ub<&IEV1HU~K9Caj4FIWuh0OL0sgU`qdWk_(X<~L$y zoSb*-tDifaInCd28m^_f1wBjastW#AI}je7o?t^Xw;OH?Meq z()=Si`DZ&^cCh?oSU_26Y%sKl(<6dn`m>plt;7cnW{+xc2DCc(L&bE=yLPfZu%2dG zQ=`Y@D#0ung5|G6^Tvg2GO1<YFx zv~_8{e8R4{rpZI6TG7uX?Dv;Osc9Z9YuVjHI^ zW5V)31hF5Nq6^^gM?^-_%xrb^>1Jm~@?zBr(xN>ws~QR4@{1*}^jxyYs*j;bF}j_( zy6`Y&mlEeoh-(Sh-doPPV<#>13@sHEvypOaw%l}qA%S>aCC#NzitP3W(D-MwT?gB1AbWo$>rnC_IWWMCE33Yq#}S`j8Cx z@AxX@ipX>J1!R5?M+etp8|YxtCCGlxn~ri#G2jwxTOk49 zQQ)j3fPKCIo%$$XY+D?kod0p*UA3Msp!IP1&d0WS%2G2F7{$bKabdSXZD5)x*v2+- z#F6juw@tb|5}OO9WOQ?O)n$HvwJeHjNZYn3;UDje-QlSLjyP2z=wJudx4IJ^B@ox- z-P?!RN68~_wJCMB-8I>{U-#VeU0205a3)3wb+Jfxc6r*wjCa)gb{STT%d>-ghZX#D zp10vHe0#KSFYHu+R8xq!cj_B)&zi_1&`+!gHlNa zUSEm@4VBiI3DC49|J0mI_c%pQC}f<|a-4b1yta%e;OevxP*TDyxe2{>dBJ_xdw(h`h!$5dzu|6nz&*iVp6G=35 zzu;v~(}XE^HnM4=DOM-c2x7M3%*BQ!ugz}kZ#8C-4t4^mGY#_k>v0J~?e<6eZA!*o z?uQw?xzq2gHDK{`N_F)N=_)0yJ$4y+a&xE;|A5VCFpv*()g>fb8upg!hoc6iCuC5} z1;>qTW9vKpx5T&xKP)GfH?Ics88W^W2rX|fW;V^FCh&sB=u&L?oqUOKe!=t??@Fj9 z>`X)=BPZu0#xj1lA-9^{zrR#v#iV=N75-lh7A*6@RzR+Kz1N1%Fl$pH-CKq9drOzo zl+VOgCz~h`F&~fvh=$+-gygKS_nr)m^a4eBPGI+a>Z`99%7TM7yC6|fQ8`Ua-ui|u zBwo3LO5Va^o_NsC@VZvnhzzN&stO;F$^t3g;K%fM+;9g`iC^odtf|DSoFI1dhp%~^ zhkXRDHC{r|>Iqb{o3LXrAldzt&Dr#%kQj+hHlWAfQ+YtP6MCUGL1+YIt zd4pY2zT0(l;DeAuojqvdB7q=H=mefd{ha@0uo>4Z0CJ5n_y}x9?zhA+L7;jZGKzFNxAaNWoVPQ_c(3p9SB5#9 zI;Tnny{VE5)}^rufLuHr3&XL@ z(;9z!j0;d;$6pEByX(VIZN35IS|t+;^^#Wz^A6zr1kzye>Vv(72uifOyZQPG-$NzW zd5P{XhHys*|M(i;V9j2YQmm)X$|SiMdH)UGA01r!@Ig_0=bS`0sv@e_U5S9-E<8hp zNSwKw=(>8}no`XVK3c|B0i~9|P#uW>`>s3u+C4)WE!@c70qd!*-S;Ge$_-zt{4#R| zH9~H6_JRBTA9UcFnlSJf}O+-Lsl!6;Zq52Nw z=}(3PMcz>bzPJGNg@ZhmFAA&o{XhdSZ$Vz*K)ZZb0 zdUNf_an#P&yOc^h?IMrwkRo6dV0ZW)io&mYKgn)Qv+I9Lbu_pxcK?@y)dLS&oHqcL zLDV_HdbLdO9GEWfkDfYwj6RnGWQC{%<1l)k44>nDE7L+Dp@Qs}Cs^gJu@JKVKr09^ zB|zwS(vZ5}7XboRj3YN9B&OITs%V`OzyEHA&dqA8g!2kyneMctukObhFp3G|?|g#q z_^3>pljvv%l$AJ)F!v(d0OP)46s;}`3yVjpZu}fU<<{F8($doW{OyLoB9H@SB_%NA za?)A5=EwU;9&5mfwDOKh4rd2x^RaO=uRM9kKJriha6`KUVB4QEODXHG3|FXk<>rlV5<25ntu5?i#bVZC^9Z%U} zx1BfW#oAiI$LTF|+4?Nj*6}0g$tKxgwft*f!XCp1>>EZWDiKA??DOeeZyNUK6bBWu zSjwW?w5|vkbHCEXGNw$w-ZBAbxb@FnWGI4{uM(P0mOX;;Se6Q0QJ=3(D`#+CW28U#x@6@GgBy|du>XNw|oQ1{KyO4QZ zFIts}a)*D-$|8m__5y?#yT7lG4=Tr3$}75FPDs4n8e=`hl^VIy1W6W9LA0l+>!KvU ztf)%IEciBz3vVdC(O5}S(?v%ab4Bs6fW3*dxG-8uNs1G92ms>0qoz91FqDV(iaH<# zilS~EafeMdV>$H&B^vel>lzxViV%jHxRZ88LQ}R6ziW-zeY77~!&GGs?VZucpFG{i z_!Cc7TY%jeVGs7_j!0+~k0j`nCj>MON|i&Zc%rV85h6fvOcFq@{2E64z0_sa8~3o! zY4+;T`?UxLUZ(|DWQyPs*B6^9BeF|$Wh(R@BY3xXU=Y57sGL}VC0X`K-0W;cb8WHK zRKij^OF?;cP4sLLjJ?vLXQijWy8>aWQMnf{kd79D7| znmy4+ZakXCP`5c!%*qz}caZ=yJ8nS@s=MO1W<}o^>ZORVZBj2!Pdhg#kb+qqc=koHz ziR^LbQ&k!kd$rY?vcL4Z*}Vi9CcX7BiWY6We;r1$-iNR)(hZAQp7FGQuAA8<4y#3E zE_L4=9uo5lG9XfogBu-35Q6w}Xr2v^MQ|w^FJCksK)aHau!1(OTHn@fpDH_!sHd)- zv6YjgjU_pU&R*+qE+r_v=37Z7dJq^WJdUI7ph26kGM@y-pZNR{J zt#v{(58$bZ$eMYEP3NKdqD(xu@@vvlgJ-(DET4aN%2FF7&Rvl|Uc5sQwlcct zwr2vdNSFEU0hK=QLpGWSC)xL#urQ3Lr}OI0OsR*zcz;)jQBrb_ec%3G-kssR#EH32 zgMb>bDm)~kt7KawD?la8=N}7L>K$fNvwZdMKIy4}k&l12e4ci2=jzT5WTG_}@=4CJ z@Xvi(Ui(<3Y(lYmv|m5`SAVTNDFvt}`9>=^$r#r39I+nToqFUa^BbE21B&3<%ge8% z>|agGwN%ax=5^fnk(2F?_qbqdpZ{%zbme;J`S4V%^vCe#x)!ztJ^A`gRMn3{j%3`X zJ4aUF`mM^qk7$pQH4m z@|7TH-e6Z$)DUc5ZqfL-Z81 zsJu>pqQv6hJ2klCOf$ysJ1;3q&PPnzVpWTzA-)(h`JuDYuM2!B7B!h*hROL`xdKME z;rY9|gQyiGM>U{FbGBG3Yn{R-0gUoVY~16I30+t37puHy>vSzukVwhYjg4`=PS#cq z&iCS8WlO0@^iX6xl2`)53?1yjUNsFm1IERlU2kc~7+LA8+i1JC0)MfIGKH1pECS+- zWr<1%0nPp%cF>0Gz^edqhaAm;HG?VUKXT3a*PrbZbx?%0%smUWrCK$ZR(`g&IdC21 zu3Sw;F$+%0|8(fz=)089q*jEenOLA9Q%H`r9f=i=E&S{p*p6>LAw)}arU+KF(%15- zm#a|(iPe2%g1t8j0YBsfwdqkK{!_blotYecHSY9cG@ay#jGGRoQ{~)9foOlZlIFx? zTxq#6kTW&j5lNE%<;tG|QJRD+e*4GLV`N_H8l-3-iw0(g({)rFwoe58eJ7qzf1)&g zuo4CN@N#iQyIuFkMfNnnO?$?(IO~^XvIp%;)MOFpds<9=>)Ze&yRKz9%4l~ zok^7p;o|E`8j9CVd^`PsY@52Gtr4dz5lF5i)Tde$J+`+8jZCdy&F^EubCA3W*pqO; zaAMSM>b0Z%i3#;>5P-c1LcNJQaNuPM!`!rVbL&>3&Xlaz4n-ByJfwTY^Z8;R`qMH3$*I_@3o3?g}%OSQ(D=iuO^J3QOC2!F1Axm*A zZ?Rgi$9w_F>3lX1=T#d0lL%zTqmtAR^^8SoA=ylkq)S2=cPhD+Zv*=Bj;_NLY;XRv zEGe!2E4`VR`h=^~g#ra$c>TfC^CNpxwauu1Wl`i6=^0XQ`&K#h3xD;KOl}~+_LbdC}Kn3@byH@t#wl*);@ zwQvMjR_{o4zur!(`yn9MIvwTAMtTPBY$JhY{L!AHW;bSNgN@Xia&OZ)sM3JV881=< zc|^R`f(^Sq3YKuwRcU%&$3S0>Q?f-Cr=~0=EY}B5JJXM515E(q(Ue!;d)CTNbB%38 zQ^qNIc2`d&#eZHHC!gHhs{PR(KS9<2EjVoP_f%K*hvJ$XnH8~}!bk^-@OcpUzo zDJpIfwFrWuCUa$Z)Hy7*`g;2!T=U7akqP3S12|<45Y(_k-v0$c6D;CVXj{=(tC_xS z8}AoxPd=^$fn?Ktw05a?R&ra+r8$y;WC@ei(1LS4E!}HxWd6%FJY*OZUg$?hOX}(@ z7Z+&pK>h3mE>Be%8Olq`$p=wmdp56e>yiR!! zr*|!t{Nx=`f(c&k6~HWh6D8tc%HU7q9Xx;8K?Q<`qsd8O zg`v^znR5z`Hqy>*4=?T3@v}}xL}5p+<4?+1h%p+%q?ou~<7P%TDukN?5*0<8XqW!B zS7f{#o{*Cr`_XTnmwS>o)njZqzxjvUDV#ucydPSHznytyi%trlfcB&aI$bO40FnacNz=rB$i`4nSf2wG0NM}}ZXxA*Iv;+d)8(-J5ra{2VDr>ubOtIQW{DN} zQlUv2A+M%E{R~aU%O-GYs6!lT>#>Va)O+sEuejN(F#nl0VfY#VN8Vvj7c4R_`>lNj z^m)rs`xdb!)68%rf+ON@0-iEoP|?Qm3Dz!Bkcb_AtPH1hZcVphVnwY_9clQ26}02^%?1;COGt$wCWAweU)X!`%N>`Jj=B*Z=L=Us?(>|2Mp}FnTH* zo)k2?_#m0BSff5t$7jWB(sX9cnRRx*jKDLJD4lb_4>Qi|a_iU}Xb(Tosw6L8=D6H# zgUFr2X%CT0ebX!c4`^}mGZ4WlK9Z3}AX3l~ar>XfroKDvnI!t%>}(qX2MKPZ}+ zbq{yAH5u5+Hfs|D!+iRu=mqfmVM)iSZf79-(PX^Y+Y-IP@R3 z+!Ylfnww(DNCdKeHH*n2y85w~^Rq#OZ?sn0eAcNORUH<>7V;HIZmz9dFEJ-u!IHCI;2Hh$J8x}G%VjlK1#8skXW=}YjN$M}5mD*Fa9r-&S%C$QDXA%RgmD^nK5DJ5npf++w#QuWa>Wx+EHI{g62|%rp^vn98i@@XQ+)~-EsVXo(e1*op5J>i zikSa1Y2(h$XlGwV)34mIC;}d67P#H+c@kGH9E~$$>lBQ=N+i@s*Wq8is<+A2Z*7j| z(*FcCD8aB;au{+yuB=VlRpNa9wHBwGxpq#=F!9~e($((w1D6?R?0<mbI=1ih0n*6gO<)FKETD;hd4_g?Jj~@&~Jt zycs1RM zi!Uqrr##|+k^OSs!bzSK4!jieUk`<%-l?L$VbzF=h}?B;^~!*m{+7v`LPm7A@+(Y7 zLdJVYw80)-)5;|pV!$xLy@dw5`r`HAw6%|H>wK7W4}eTJbqtcsLWoiv^7y7hSmPjy zJI)74_9nyOl&E!JoIn|r&ZSiSd^*eJj)ofii(*CtdZYYqRhOO}lIerIdf(i}6;3(= z^rTLMy%CTYy`{KVK_^XKt#lTR!Iiyb0P7}X7;U4rL!Bvc&uUTJuv!K8tQMLyha=LR zG^S7j{=#db-Kfb`dXz?uiIJrDTpjT(U-^FjWAwc?hI}p>{?q_*mVvd$J--X+A#Xl~ zqHxO3zv_kyRX2~!V!69D@O=~iPf8uGBvyl@LXd!f&)Jz61jbqh(uJndkvCw zl&=bGg~^bTY8mSS#L?*Uj^O1DA1ZVLr(i#y6zNe?@fbwg1k8quM%{6Rk z0T)};lQhX+y!#OW)x8v`>X!HG)`&rCydQ^x8&WznLate$vG8hUNbqiW5FQ94cn7fF zpB9dQ2v_2;L*oBYjvD1+--&P#9?pxwD{x?-mTU)CX`zGrhGUR7fSJ1KIU$*znP$Xy z_7?i-aYH6TRdy-=^85V#uTv3-0uZ7Ff(4TPlX`)eZtG!Cw~QbgccTKfxl<+Cj9^~g zl@Iw1!maa}PM;Fz4`JZ=y=}wcVykc+yt;dKlY9zvHXu`wRq*8?$N0a-5ajCf74 zHeG?D#N;q=YKGZ)q_(gbuJnIvZ&cz|d6x91QO;`-V|^bW?ASAGZF_&2(ZZ%1tck}) zM;}*Vn%z^sLOS>_N1d1f0+r}!XV^h+Z!fsW4yH;$#mN?14@o>SFbPJ*OWzk3XBLq+@0aC?@8>Eu(_z) z<>L9GD}*Z%+0A}&Kf}QwW?-pg+LdCxm41`ycX{4hqow2h#rxcg@RDyumB;CNOtEVAOCwuX~Sp*Llt<&*$8LcSWORE14Qk;36zCDefY%wSy zou+&F@_~OemH&M|v)ky>iV{c5x2d{Xz#~T9=%5>G^f^uE54hUk_C3gd076O}t(jO| zIXcZ>W)pZ2xlfZ>_lLpR!Sxfs!(`^?(|_>kgR1>ML)Bm@l+?O8gq6&Hk6gHaR3dEn zjMBBS-g=qE;anVin<=>EJAj2yxGmAW$q-yKNcrFN3b?7c0~*6%#KjZY@-j2`2UGr+ zdH@r_m&(&jF>62%>Af}8DB=8)hUO2q10#FMv!ef|3@fmXk@QJp^Q=WB>pOR|V zD~JYozcK^MeMPzREV~SF&sB|OVNxS;#E{pi6yIo%wl&ZfqkOPA6TxHZWcAMJKi^6| zM+?8RN);9`&85D7?-|W;A_Dp-ov)2$65YEhzs?&kz{+Sw8Jjp%T(*O3p8N61~Wki7nHC2zv-^0!ov z(#WSi_--#m0FAx@{Fc)~i^#pD9;Cv7Ca~M~E8-g7&-m-x3x2w$1zFtt Date: Wed, 7 Jan 2026 09:51:21 +0100 Subject: [PATCH 2/2] treat --runtime-tags parameter as an override on tag-per-tag level --- internal/framework/framework.go | 10 ---- internal/framework/framework_test.go | 61 -------------------- internal/framework/minitest.go | 9 ++- internal/framework/rspec.go | 9 ++- internal/runner/dd_test_optimization.go | 22 ++++--- internal/runner/dd_test_optimization_test.go | 23 ++++++-- 6 files changed, 44 insertions(+), 90 deletions(-) diff --git a/internal/framework/framework.go b/internal/framework/framework.go index 197094b..f839aa0 100644 --- a/internal/framework/framework.go +++ b/internal/framework/framework.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "log/slog" - "maps" "os" "path/filepath" "time" @@ -89,15 +88,6 @@ func globTestFiles(pattern string) ([]string, error) { return matches, nil } -// mergeEnvMaps merges base env vars with overrides. -// Values in overrides take precedence over base values. -func mergeEnvMaps(base, overrides map[string]string) map[string]string { - result := make(map[string]string) - maps.Copy(result, base) - maps.Copy(result, overrides) - return result -} - // BaseDiscoveryEnv returns environment variables required for all test discovery processes. // These env vars ensure the test framework runs in discovery mode without requiring // actual Datadog credentials or agent connectivity. diff --git a/internal/framework/framework_test.go b/internal/framework/framework_test.go index cff3cef..65c10ae 100644 --- a/internal/framework/framework_test.go +++ b/internal/framework/framework_test.go @@ -28,64 +28,3 @@ func TestBaseDiscoveryEnv(t *testing.T) { t.Errorf("expected %d env vars, got %d", len(expectedVars), len(env)) } } - -func TestMergeEnvMaps(t *testing.T) { - t.Run("merges two maps", func(t *testing.T) { - platform := map[string]string{"A": "1", "B": "2"} - additional := map[string]string{"C": "3", "D": "4"} - - result := mergeEnvMaps(platform, additional) - - if len(result) != 4 { - t.Errorf("expected 4 keys, got %d", len(result)) - } - if result["A"] != "1" || result["B"] != "2" || result["C"] != "3" || result["D"] != "4" { - t.Errorf("unexpected result: %v", result) - } - }) - - t.Run("additional overrides platform", func(t *testing.T) { - platform := map[string]string{"A": "platform", "B": "2"} - additional := map[string]string{"A": "additional", "C": "3"} - - result := mergeEnvMaps(platform, additional) - - if result["A"] != "additional" { - t.Errorf("expected additional to override platform, got %q", result["A"]) - } - if result["B"] != "2" { - t.Errorf("expected B to be preserved, got %q", result["B"]) - } - if result["C"] != "3" { - t.Errorf("expected C to be present, got %q", result["C"]) - } - }) - - t.Run("handles nil maps", func(t *testing.T) { - result := mergeEnvMaps(nil, nil) - if result == nil { - t.Error("expected non-nil result") - } - if len(result) != 0 { - t.Errorf("expected empty map, got %v", result) - } - }) - - t.Run("handles nil platform", func(t *testing.T) { - additional := map[string]string{"A": "1"} - result := mergeEnvMaps(nil, additional) - - if result["A"] != "1" { - t.Errorf("expected A=1, got %q", result["A"]) - } - }) - - t.Run("handles nil additional", func(t *testing.T) { - platform := map[string]string{"A": "1"} - result := mergeEnvMaps(platform, nil) - - if result["A"] != "1" { - t.Errorf("expected A=1, got %q", result["A"]) - } - }) -} diff --git a/internal/framework/minitest.go b/internal/framework/minitest.go index d25da85..0f3ddf1 100644 --- a/internal/framework/minitest.go +++ b/internal/framework/minitest.go @@ -3,6 +3,7 @@ package framework import ( "context" "log/slog" + "maps" "os" "strings" @@ -51,7 +52,9 @@ func (m *Minitest) DiscoverTests(ctx context.Context) ([]testoptimization.Test, slog.Debug("Using test discovery pattern", "pattern", pattern) // Merge env maps: platform env -> base discovery env - envMap := mergeEnvMaps(m.platformEnv, BaseDiscoveryEnv()) + envMap := make(map[string]string) + maps.Copy(envMap, m.platformEnv) + maps.Copy(envMap, BaseDiscoveryEnv()) if isRails { args = append(args, pattern) @@ -109,7 +112,9 @@ func (m *Minitest) RunTests(ctx context.Context, testFiles []string, envMap map[ } } - mergedEnv := mergeEnvMaps(m.platformEnv, envMap) + mergedEnv := make(map[string]string) + maps.Copy(mergedEnv, m.platformEnv) + maps.Copy(mergedEnv, envMap) return m.executor.Run(ctx, command, args, mergedEnv) } diff --git a/internal/framework/rspec.go b/internal/framework/rspec.go index 4b10d3d..7289753 100644 --- a/internal/framework/rspec.go +++ b/internal/framework/rspec.go @@ -3,6 +3,7 @@ package framework import ( "context" "log/slog" + "maps" "os" "github.com/DataDog/ddtest/internal/ext" @@ -51,7 +52,9 @@ func (r *RSpec) DiscoverTests(ctx context.Context) ([]testoptimization.Test, err args = append(args, "--pattern", pattern) // Merge env maps: platform env -> base discovery env - envMap := mergeEnvMaps(r.platformEnv, BaseDiscoveryEnv()) + envMap := make(map[string]string) + maps.Copy(envMap, r.platformEnv) + maps.Copy(envMap, BaseDiscoveryEnv()) slog.Info("Using test discovery pattern", "pattern", pattern) slog.Info("Discovering tests with command", "command", name, "args", args) @@ -92,7 +95,9 @@ func (r *RSpec) RunTests(ctx context.Context, testFiles []string, envMap map[str slog.Info("Running tests with command", "command", command, "args", args) args = append(args, testFiles...) - mergedEnv := mergeEnvMaps(r.platformEnv, envMap) + mergedEnv := make(map[string]string) + maps.Copy(mergedEnv, r.platformEnv) + maps.Copy(mergedEnv, envMap) return r.executor.Run(ctx, command, args, mergedEnv) } diff --git a/internal/runner/dd_test_optimization.go b/internal/runner/dd_test_optimization.go index 06ebe3a..0b5573a 100644 --- a/internal/runner/dd_test_optimization.go +++ b/internal/runner/dd_test_optimization.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "maps" "time" "github.com/DataDog/ddtest/internal/settings" @@ -17,20 +18,23 @@ func (tr *TestRunner) PrepareTestOptimization(ctx context.Context) error { return fmt.Errorf("failed to detect platform: %w", err) } - // Check if runtime tags override is provided - tags, err := settings.GetRuntimeTagsMap() + // Get platform-detected tags first + tags, err := detectedPlatform.CreateTagsMap() + if err != nil { + return fmt.Errorf("failed to create platform tags: %w", err) + } + + // Check if runtime tags override is provided and merge onto detected tags + overrideTags, err := settings.GetRuntimeTagsMap() if err != nil { return fmt.Errorf("failed to parse runtime tags override: %w", err) } - if tags != nil { - slog.Info("Using runtime tags override from --runtime-tags", "runtimeTags", tags) + if overrideTags != nil { + // Merge override tags onto detected tags (override values take precedence) + maps.Copy(tags, overrideTags) + slog.Info("Merged runtime tags override from --runtime-tags", "overrideTags", overrideTags, "mergedTags", tags) } else { - // Use platform-detected tags - tags, err = detectedPlatform.CreateTagsMap() - if err != nil { - return fmt.Errorf("failed to create platform tags: %w", err) - } slog.Info("Preparing test optimization data", "runtimeTags", tags, "platform", detectedPlatform.Name()) } diff --git a/internal/runner/dd_test_optimization_test.go b/internal/runner/dd_test_optimization_test.go index adb7ad7..cedd0d1 100644 --- a/internal/runner/dd_test_optimization_test.go +++ b/internal/runner/dd_test_optimization_test.go @@ -314,8 +314,8 @@ func TestTestRunner_PrepareTestOptimization_AllTestsSkipped(t *testing.T) { func TestTestRunner_PrepareTestOptimization_RuntimeTagsOverride(t *testing.T) { ctx := context.Background() - // Set runtime tags override via environment variable - overrideTags := `{"os.platform":"linux","runtime.version":"3.2.0","language":"ruby"}` + // Set runtime tags override via environment variable - only override some tags + overrideTags := `{"os.platform":"linux","runtime.version":"3.2.0"}` _ = os.Setenv("DD_TEST_OPTIMIZATION_RUNNER_RUNTIME_TAGS", overrideTags) defer func() { _ = os.Unsetenv("DD_TEST_OPTIMIZATION_RUNNER_RUNTIME_TAGS") @@ -331,11 +331,13 @@ func TestTestRunner_PrepareTestOptimization_RuntimeTagsOverride(t *testing.T) { }, } - // Platform tags should be different from override tags + // Platform tags should have more tags than the override mockPlatform := &MockPlatform{ PlatformName: "ruby", Tags: map[string]string{ "os.platform": "darwin", + "os.architecture": "arm64", + "runtime.name": "ruby", "runtime.version": "3.3.0", "language": "ruby", }, @@ -358,12 +360,12 @@ func TestTestRunner_PrepareTestOptimization_RuntimeTagsOverride(t *testing.T) { t.Errorf("PrepareTestOptimization() should not return error, got: %v", err) } - // Verify optimization client was initialized with override tags, not platform tags + // Verify optimization client was initialized if !mockOptimizationClient.InitializeCalled { t.Error("PrepareTestOptimization() should initialize optimization client") } - // Check that override tags were used + // Check that override tags replaced the detected values if mockOptimizationClient.Tags["os.platform"] != "linux" { t.Errorf("Expected os.platform to be 'linux' from override, got %q", mockOptimizationClient.Tags["os.platform"]) } @@ -372,8 +374,17 @@ func TestTestRunner_PrepareTestOptimization_RuntimeTagsOverride(t *testing.T) { t.Errorf("Expected runtime.version to be '3.2.0' from override, got %q", mockOptimizationClient.Tags["runtime.version"]) } + // Check that detected tags NOT in override were preserved + if mockOptimizationClient.Tags["os.architecture"] != "arm64" { + t.Errorf("Expected os.architecture to be 'arm64' from detected tags (not overridden), got %q", mockOptimizationClient.Tags["os.architecture"]) + } + + if mockOptimizationClient.Tags["runtime.name"] != "ruby" { + t.Errorf("Expected runtime.name to be 'ruby' from detected tags (not overridden), got %q", mockOptimizationClient.Tags["runtime.name"]) + } + if mockOptimizationClient.Tags["language"] != "ruby" { - t.Errorf("Expected language to be 'ruby' from override, got %q", mockOptimizationClient.Tags["language"]) + t.Errorf("Expected language to be 'ruby' from detected tags (not overridden), got %q", mockOptimizationClient.Tags["language"]) } }