Skip to content

Commit 56ba7fc

Browse files
committed
tests for Android profiling (#1949)
1 parent 1f7a643 commit 56ba7fc

40 files changed

Lines changed: 746 additions & 53 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Unreleased
44

5-
* Feat: Add Android profiling traces #1897
5+
* Feat: Add Android profiling traces #1897 and its tests #1949
66

77
## 5.6.2
88

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.sentry
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertNull
5+
6+
class NoOpTransactionProfilerTest {
7+
private var profiler = NoOpTransactionProfiler.getInstance()
8+
9+
@Test
10+
fun `onTransactionStart does not throw`() =
11+
profiler.onTransactionStart(NoOpTransaction.getInstance())
12+
13+
@Test
14+
fun `onTransactionFinish returns null`() =
15+
assertNull(profiler.onTransactionFinish(NoOpTransaction.getInstance()))
16+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package io.sentry.util
2+
3+
import java.io.File
4+
import java.nio.file.Files
5+
import kotlin.test.Test
6+
import kotlin.test.assertEquals
7+
import kotlin.test.assertFalse
8+
import kotlin.test.assertNull
9+
import kotlin.test.assertTrue
10+
11+
class FileUtilsTest {
12+
13+
@Test
14+
fun `deleteRecursively returns true on null file`() {
15+
assertTrue(FileUtils.deleteRecursively(null))
16+
}
17+
18+
@Test
19+
fun `deleteRecursively returns true on non-existing file`() {
20+
assertTrue(FileUtils.deleteRecursively(File("")))
21+
}
22+
23+
@Test
24+
fun `deleteRecursively deletes a simple file`() {
25+
val f = Files.createTempFile("here", "test").toFile()
26+
assertTrue(f.exists())
27+
assertTrue(FileUtils.deleteRecursively(f))
28+
assertFalse(f.exists())
29+
}
30+
31+
@Test
32+
fun `deleteRecursively deletes a folder`() {
33+
// Setup vars
34+
val rootDir = Files.createTempDirectory("here").toFile()
35+
val rootChild = File(rootDir, "test")
36+
val innerDir = File(rootDir, "dir2")
37+
val innerChild = File(innerDir, "test")
38+
39+
// Create directories and files
40+
rootChild.createNewFile()
41+
innerDir.mkdir()
42+
innerChild.createNewFile()
43+
44+
// Assert dirs and files exist
45+
assertTrue(rootDir.exists() && rootDir.isDirectory)
46+
assertTrue(rootChild.exists())
47+
assertTrue(innerDir.exists() && innerDir.isDirectory)
48+
assertTrue(innerChild.exists())
49+
50+
// Assert deletion returns true
51+
assertTrue(FileUtils.deleteRecursively(rootDir))
52+
53+
// Assert dirs and files no longer exist
54+
assertFalse(rootChild.exists())
55+
assertFalse(rootDir.exists())
56+
assertFalse(innerChild.exists())
57+
assertFalse(innerDir.exists())
58+
}
59+
60+
@Test
61+
fun `readText returns null on null, non existing or unreadable file`() {
62+
val f = File("here", "test")
63+
val unreadableFile = Files.createTempFile("here", "test").toFile()
64+
unreadableFile.setReadable(false)
65+
assertNull(FileUtils.readText(null))
66+
assertNull(FileUtils.readText(f))
67+
assertNull(FileUtils.readText(unreadableFile))
68+
}
69+
70+
@Test
71+
fun `readText returns the content of a file`() {
72+
val f = Files.createTempFile("here", "test").toFile()
73+
val text = "Lorem ipsum dolor sit amet\nLorem ipsum dolor sit amet"
74+
f.writeText(text)
75+
assertEquals(text, FileUtils.readText(f))
76+
}
77+
}

sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.concurrent.Future;
2222
import org.jetbrains.annotations.NotNull;
2323
import org.jetbrains.annotations.Nullable;
24+
import org.jetbrains.annotations.TestOnly;
2425

2526
final class AndroidTransactionProfiler implements ITransactionProfiler {
2627

@@ -59,6 +60,10 @@ public AndroidTransactionProfiler(
5960
Objects.requireNonNull(buildInfoProvider, "The BuildInfoProvider is required.");
6061
this.packageInfo = ContextUtils.getPackageInfo(context, options.getLogger());
6162
final String tracesFilesDirPath = options.getProfilingTracesDirPath();
63+
if (!options.isProfilingEnabled()) {
64+
options.getLogger().log(SentryLevel.INFO, "Profiling is disabled in options.");
65+
return;
66+
}
6267
if (tracesFilesDirPath == null || tracesFilesDirPath.isEmpty()) {
6368
options
6469
.getLogger()
@@ -90,7 +95,7 @@ public synchronized void onTransactionStart(@NotNull ITransaction transaction) {
9095

9196
// traceFilesDir is null or intervalUs is 0 only if there was a problem in the constructor, but
9297
// we already logged that
93-
if (traceFilesDir == null || intervalUs == 0) {
98+
if (traceFilesDir == null || intervalUs == 0 || !traceFilesDir.exists()) {
9499
return;
95100
}
96101

@@ -225,7 +230,7 @@ public synchronized void onTransactionStart(@NotNull ITransaction transaction) {
225230
buildInfoProvider.getModel(),
226231
buildInfoProvider.getVersionRelease(),
227232
buildInfoProvider.isEmulator(),
228-
CpuInfoUtils.readMaxFrequencies(),
233+
CpuInfoUtils.getInstance().readMaxFrequencies(),
229234
totalMem,
230235
options.getProguardUuid(),
231236
versionName,
@@ -253,4 +258,9 @@ public synchronized void onTransactionStart(@NotNull ITransaction transaction) {
253258
return null;
254259
}
255260
}
261+
262+
@TestOnly
263+
void setTimedOutProfilingData(@Nullable ProfilingTraceData data) {
264+
this.timedOutProfilingData = data;
265+
}
256266
}

sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,38 @@
77
import java.util.List;
88
import org.jetbrains.annotations.ApiStatus;
99
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.TestOnly;
11+
import org.jetbrains.annotations.VisibleForTesting;
1012

1113
@ApiStatus.Internal
1214
public final class CpuInfoUtils {
1315

14-
private static final @NotNull String SYSTEM_CPU_PATH = "/sys/devices/system/cpu/";
15-
private static final @NotNull String CPUINFO_MAX_FREQ_PATH = "cpufreq/cpuinfo_max_freq";
16+
private static final CpuInfoUtils instance = new CpuInfoUtils();
17+
18+
public static CpuInfoUtils getInstance() {
19+
return instance;
20+
}
21+
22+
private CpuInfoUtils() {}
23+
24+
private static final @NotNull String SYSTEM_CPU_PATH = "/sys/devices/system/cpu";
25+
26+
@VisibleForTesting
27+
static final @NotNull String CPUINFO_MAX_FREQ_PATH = "cpufreq/cpuinfo_max_freq";
1628

1729
/** Cached max frequencies to avoid reading files multiple times */
18-
private static @NotNull List<String> cpuMaxFrequenciesMhz = new ArrayList<>();
30+
private final @NotNull List<Integer> cpuMaxFrequenciesMhz = new ArrayList<>();
1931

2032
/**
2133
* Read the max frequency of each core of the cpu and returns it in Mhz
2234
*
2335
* @return A list with the frequency of each core of the cpu in Mhz
2436
*/
25-
public static @NotNull List<String> readMaxFrequencies() {
37+
public @NotNull List<Integer> readMaxFrequencies() {
2638
if (!cpuMaxFrequenciesMhz.isEmpty()) {
2739
return cpuMaxFrequenciesMhz;
2840
}
29-
File[] cpuDirs = new File(SYSTEM_CPU_PATH).listFiles();
41+
File[] cpuDirs = new File(getSystemCpuPath()).listFiles();
3042
if (cpuDirs == null) {
3143
return new ArrayList<>();
3244
}
@@ -37,7 +49,7 @@ public final class CpuInfoUtils {
3749

3850
if (!cpuMaxFreqFile.exists() || !cpuMaxFreqFile.canRead()) continue;
3951

40-
long khz = 0;
52+
long khz;
4153
try {
4254
String content = FileUtils.readText(cpuMaxFreqFile);
4355
if (content == null) continue;
@@ -47,8 +59,19 @@ public final class CpuInfoUtils {
4759
} catch (IOException e) {
4860
continue;
4961
}
50-
cpuMaxFrequenciesMhz.add(Long.toString(khz / 1000));
62+
cpuMaxFrequenciesMhz.add((int) (khz / 1000));
5163
}
5264
return cpuMaxFrequenciesMhz;
5365
}
66+
67+
@VisibleForTesting
68+
@NotNull
69+
String getSystemCpuPath() {
70+
return SYSTEM_CPU_PATH;
71+
}
72+
73+
@TestOnly
74+
final void clear() {
75+
cpuMaxFrequenciesMhz.clear();
76+
}
5477
}

sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,29 @@ class AndroidOptionsInitializerTest {
143143
)
144144
}
145145

146+
@Test
147+
fun `profilingTracesDirPath should be set at initialization`() {
148+
val sentryOptions = SentryAndroidOptions()
149+
val mockContext = createMockContext()
150+
151+
AndroidOptionsInitializer.init(sentryOptions, mockContext)
152+
153+
assertTrue(sentryOptions.profilingTracesDirPath?.endsWith("${File.separator}cache${File.separator}sentry${File.separator}profiling_traces")!!)
154+
assertFalse(File(sentryOptions.profilingTracesDirPath!!).exists())
155+
}
156+
157+
@Test
158+
fun `profilingTracesDirPath should be created and cleared when profiling is enabled`() {
159+
val sentryOptions = SentryAndroidOptions()
160+
val mockContext = createMockContext()
161+
sentryOptions.isProfilingEnabled = true
162+
163+
AndroidOptionsInitializer.init(sentryOptions, mockContext)
164+
165+
assertTrue(File(sentryOptions.profilingTracesDirPath!!).exists())
166+
assertTrue(File(sentryOptions.profilingTracesDirPath!!).list()!!.isEmpty())
167+
}
168+
146169
@Test
147170
fun `outboxDir should be set at initialization`() {
148171
fixture.initSut()
@@ -218,6 +241,17 @@ class AndroidOptionsInitializerTest {
218241
assertTrue(fixture.sentryOptions.transportGate is AndroidTransportGate)
219242
}
220243

244+
@Test
245+
fun `init should set Android transaction profiler`() {
246+
val sentryOptions = SentryAndroidOptions()
247+
val mockContext = createMockContext()
248+
249+
AndroidOptionsInitializer.init(sentryOptions, mockContext)
250+
251+
assertNotNull(sentryOptions.transactionProfiler)
252+
assertTrue(sentryOptions.transactionProfiler is AndroidTransactionProfiler)
253+
}
254+
221255
@Test
222256
fun `NdkIntegration will load SentryNdk class and add to the integration list`() {
223257
fixture.initSutWithClassLoader(classToLoad = SentryNdk::class.java)

0 commit comments

Comments
 (0)