• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.thread.utils;
18 
19 import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast;
20 
21 import static org.junit.Assume.assumeTrue;
22 
23 import android.content.Context;
24 import android.os.SystemProperties;
25 import android.os.VintfRuntimeInfo;
26 
27 import androidx.test.core.app.ApplicationProvider;
28 
29 import org.junit.rules.TestRule;
30 import org.junit.runner.Description;
31 import org.junit.runners.model.Statement;
32 
33 import java.lang.annotation.Annotation;
34 import java.lang.annotation.ElementType;
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.lang.annotation.Target;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
40 
41 /**
42  * A rule used to skip Thread tests when the device doesn't support a specific feature indicated by
43  * {@code ThreadFeatureCheckerRule.Requires*}.
44  */
45 public final class ThreadFeatureCheckerRule implements TestRule {
46     private static final String KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED = "5.15.0";
47     private static final int KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED = 14;
48 
49     /**
50      * Annotates a test class or method requires the Thread feature to run.
51      *
52      * <p>In Absence of the Thread feature, the test class or method will be ignored.
53      */
54     @Retention(RetentionPolicy.RUNTIME)
55     @Target({ElementType.METHOD, ElementType.TYPE})
56     public @interface RequiresThreadFeature {}
57 
58     /**
59      * Annotates a test class or method requires the kernel IPv6 multicast routing feature to run.
60      *
61      * <p>In Absence of the multicast routing feature, the test class or method will be ignored.
62      */
63     @Retention(RetentionPolicy.RUNTIME)
64     @Target({ElementType.METHOD, ElementType.TYPE})
65     public @interface RequiresIpv6MulticastRouting {}
66 
67     /**
68      * Annotates a test class or method requires the simulation Thread device (i.e. ot-cli-ftd) to
69      * run.
70      *
71      * <p>In Absence of the simulation device, the test class or method will be ignored.
72      */
73     @Retention(RetentionPolicy.RUNTIME)
74     @Target({ElementType.METHOD, ElementType.TYPE})
75     public @interface RequiresSimulationThreadDevice {}
76 
77     @Override
apply(final Statement base, Description description)78     public Statement apply(final Statement base, Description description) {
79         return new Statement() {
80             @Override
81             public void evaluate() throws Throwable {
82                 if (hasAnnotation(RequiresThreadFeature.class, description)) {
83                     assumeTrue(
84                             "Skipping test because the Thread feature is unavailable",
85                             hasThreadFeature());
86                 }
87 
88                 if (hasAnnotation(RequiresIpv6MulticastRouting.class, description)) {
89                     assumeTrue(
90                             "Skipping test because kernel IPv6 multicast routing is unavailable",
91                             hasIpv6MulticastRouting());
92                 }
93 
94                 if (hasAnnotation(RequiresSimulationThreadDevice.class, description)) {
95                     assumeTrue(
96                             "Skipping test because simulation Thread device is unavailable",
97                             hasSimulationThreadDevice());
98                 }
99 
100                 base.evaluate();
101             }
102         };
103     }
104 
105     /** Returns {@code true} if a test method or the test class is annotated with annotation. */
106     private <T extends Annotation> boolean hasAnnotation(
107             Class<T> annotationClass, Description description) {
108         // Method annotation
109         boolean hasAnnotation = description.getAnnotation(annotationClass) != null;
110 
111         // Class annotation
112         Class<?> clazz = description.getTestClass();
113         while (!hasAnnotation && clazz != Object.class) {
114             hasAnnotation |= clazz.getAnnotation(annotationClass) != null;
115             clazz = clazz.getSuperclass();
116         }
117 
118         return hasAnnotation;
119     }
120 
121     /** Returns {@code true} if this device has the Thread feature supported. */
122     private static boolean hasThreadFeature() {
123         final Context context = ApplicationProvider.getApplicationContext();
124 
125         // Use service name rather than `ThreadNetworkManager.class` to avoid
126         // `ClassNotFoundException` on U- devices.
127         return context.getSystemService("thread_network") != null;
128     }
129 
130     /**
131      * Returns {@code true} if this device has the kernel IPv6 multicast routing feature enabled.
132      */
133     private static boolean hasIpv6MulticastRouting() {
134         // The kernel IPv6 multicast routing (i.e. IPV6_MROUTE) is enabled on kernel version
135         // android14-5.15.0 and later
136         return isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)
137                 && isKernelAndroidVersionAtLeast(
138                         KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED);
139     }
140 
141     /**
142      * Returns {@code true} if the android version in the kernel version of this device is equal to
143      * or larger than the given {@code minVersion}.
144      */
145     private static boolean isKernelAndroidVersionAtLeast(int minVersion) {
146         final String osRelease = VintfRuntimeInfo.getOsRelease();
147         final Pattern pattern = Pattern.compile("android(\\d+)");
148         Matcher matcher = pattern.matcher(osRelease);
149 
150         if (matcher.find()) {
151             int version = Integer.parseInt(matcher.group(1));
152             return (version >= minVersion);
153         }
154         return false;
155     }
156 
157     /** Returns {@code true} if the simulation Thread device is supported. */
158     private static boolean hasSimulationThreadDevice() {
159         // Simulation radio is supported on only Cuttlefish
160         return SystemProperties.get("ro.product.model").startsWith("Cuttlefish");
161     }
162 }
163