Skip to content

Commit ea02984

Browse files
committed
Add OSGi integration test that resolves semconv bundles in Felix
1 parent 6a07130 commit ea02984

3 files changed

Lines changed: 165 additions & 0 deletions

File tree

osgi-test/build.gradle.kts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import aQute.bnd.gradle.Bundle
2+
import aQute.bnd.gradle.Resolve
3+
import aQute.bnd.gradle.TestOSGi
4+
5+
plugins {
6+
id("otel.java-conventions")
7+
}
8+
9+
description = "OpenTelemetry Semantic Conventions OSGi Integration Tests"
10+
otelJava.moduleName.set("io.opentelemetry.semconv.integration.tests.osgi")
11+
// This module runs OSGi tests against the published semconv bundles; it does not publish an OSGi
12+
// bundle itself.
13+
otelJava.osgiEnabled.set(false)
14+
15+
// The semconv bundles Import-Package io.opentelemetry.api.common, so resolution needs an
16+
// opentelemetry-api that is itself a proper OSGi bundle. OSGi metadata was added to the core
17+
// artifacts in opentelemetry-java#8417, well after the 1.33.0 compileOnly baseline pinned in
18+
// :dependencyManagement, so we force a recent version on this test module's classpath only.
19+
val osgiOtelApiVersion = "1.63.0"
20+
21+
configurations.configureEach {
22+
resolutionStrategy {
23+
force("io.opentelemetry:opentelemetry-api:$osgiOtelApiVersion")
24+
}
25+
}
26+
27+
dependencies {
28+
testImplementation(project(":semconv"))
29+
testImplementation(project(":semconv-incubating"))
30+
// Provides the io.opentelemetry.api.* packages the semconv bundles import, as an OSGi bundle.
31+
testImplementation("io.opentelemetry:opentelemetry-api:$osgiOtelApiVersion")
32+
33+
testImplementation("org.osgi:org.osgi.test.junit5:1.3.0")
34+
// Provided by the OSGi framework at runtime.
35+
testCompileOnly("org.osgi:osgi.core:8.0.0")
36+
37+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
38+
testRuntimeOnly("org.apache.felix:org.apache.felix.framework:7.0.5")
39+
// opentelemetry-common (pulled in transitively by opentelemetry-api) declares
40+
// Require-Capability: osgi.extender=osgi.serviceloader.processor, so the container needs Aries
41+
// SPI Fly to resolve even though semconv itself has no ServiceLoader providers.
42+
testRuntimeOnly("org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle:1.3.7")
43+
}
44+
45+
// The testing bundle (our JUnit tests + Test-Cases header) is booted inside a real Felix container
46+
// via bnd's Bundle -> Resolve -> TestOSGi task chain, modeled on opentelemetry-java's
47+
// integration-tests/osgi module.
48+
val bsn = "opentelemetry-semconv-osgi-testing"
49+
val runee = "JavaSE-${java.toolchain.languageVersion.get()}"
50+
val testRuntimeClasspath = sourceSets.test.get().runtimeClasspath
51+
52+
val testingBundle = tasks.register<Bundle>("testingBundle") {
53+
archiveClassifier.set("testing")
54+
from(sourceSets.test.get().output)
55+
bundle {
56+
// BND analyses compileClasspath by default; use the runtime classpath so testImplementation
57+
// deps (junit-jupiter, assertj) are visible and Test-Cases gets populated.
58+
classpath(testRuntimeClasspath)
59+
bnd(
60+
"Bundle-SymbolicName: $bsn",
61+
"Test-Cases: \${classes;HIERARCHY_INDIRECTLY_ANNOTATED;org.junit.platform.commons.annotation.Testable;CONCRETE}",
62+
)
63+
}
64+
}
65+
66+
val inputBndrun = layout.buildDirectory.file("bndrun/test.bndrun")
67+
val generateBndrun = tasks.register("generateBndrun") {
68+
// Local copies so the doLast closure captures only serializable values (String, Provider), not
69+
// the enclosing Kotlin build-script object (required by the configuration cache).
70+
val bndrunFile = inputBndrun
71+
val bndrunContent =
72+
"""
73+
|-tester: biz.aQute.tester.junit-platform
74+
|-runfw: org.apache.felix.framework
75+
|-runee: $runee
76+
|
77+
|-runrequires: \
78+
| bnd.identity;id='$bsn',\
79+
| bnd.identity;id='junit-jupiter-engine',\
80+
| bnd.identity;id='junit-platform-launcher'
81+
""".trimMargin()
82+
inputs.property("content", bndrunContent)
83+
outputs.file(bndrunFile)
84+
doLast {
85+
bndrunFile.get().asFile.apply { parentFile.mkdirs() }.writeText(bndrunContent)
86+
}
87+
}
88+
89+
val resolvedBndrun = layout.buildDirectory.file("test.bndrun")
90+
val resolve = tasks.register<Resolve>("resolve") {
91+
dependsOn(testingBundle, generateBndrun)
92+
description = "Resolve the semconv OSGi test suite"
93+
group = JavaBasePlugin.VERIFICATION_GROUP
94+
bndrun = inputBndrun.get().asFile
95+
outputBndrun = resolvedBndrun
96+
bundles = files(testRuntimeClasspath, testingBundle.get().archiveFile)
97+
// The resolved bndrun embeds an absolute path to the input, so it is not safe to share via cache.
98+
outputs.cacheIf { false }
99+
}
100+
101+
tasks.register<TestOSGi>("testOsgi") {
102+
dependsOn(resolve)
103+
description = "Run the semconv OSGi test suite inside an Apache Felix container"
104+
group = JavaBasePlugin.VERIFICATION_GROUP
105+
bndrun = resolvedBndrun
106+
bundles = files(testRuntimeClasspath, testingBundle.get().archiveFile)
107+
// BND reports success when zero tests ran (e.g. if bundles failed to start). Fail explicitly.
108+
val testResultsDir = layout.buildDirectory.dir("test-results/$name")
109+
doLast {
110+
check(testResultsDir.get().asFile.listFiles()?.isNotEmpty() == true) {
111+
"No OSGi test results found — bundles may have failed to start. Check the output above."
112+
}
113+
}
114+
}
115+
116+
tasks {
117+
named<Jar>("jar") {
118+
enabled = false
119+
}
120+
named<Test>("test") {
121+
// Replace plain JUnit execution with the in-container OSGi test.
122+
actions.clear()
123+
dependsOn("testOsgi")
124+
}
125+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.semconv.integrationtest.osgi;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import io.opentelemetry.api.common.AttributeKey;
11+
import io.opentelemetry.semconv.SchemaUrls;
12+
import io.opentelemetry.semconv.ServiceAttributes;
13+
import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.api.extension.ExtendWith;
16+
import org.osgi.test.junit5.context.BundleContextExtension;
17+
18+
/**
19+
* Boots the semconv and semconv-incubating bundles inside a real OSGi (Apache Felix) container and
20+
* exercises their exported packages. If either bundle's generated OSGi metadata is wrong (missing
21+
* exports, unsatisfiable imports), the bundle fails to resolve and these tests never run — which
22+
* the build treats as a failure.
23+
*/
24+
@ExtendWith(BundleContextExtension.class)
25+
public class SemconvOsgiTest {
26+
27+
@Test
28+
public void stableAttributesAreUsable() {
29+
AttributeKey<String> serviceName = ServiceAttributes.SERVICE_NAME;
30+
assertThat(serviceName.getKey()).isEqualTo("service.name");
31+
assertThat(SchemaUrls.V1_41_1).isEqualTo("https://opentelemetry.io/schemas/1.41.1");
32+
}
33+
34+
@Test
35+
public void incubatingAttributesAreUsable() {
36+
AttributeKey<String> instanceId = ServiceIncubatingAttributes.SERVICE_INSTANCE_ID;
37+
assertThat(instanceId.getKey()).isEqualTo("service.instance.id");
38+
}
39+
}

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,4 @@ rootProject.name = "semantic-conventions-java"
6060
include(":dependencyManagement")
6161
include(":semconv-incubating")
6262
include(":semconv")
63+
include(":osgi-test")

0 commit comments

Comments
 (0)