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 platform.test.motion.golden 18 19 import org.json.JSONException 20 import org.json.JSONObject 21 22 /** 23 * Describes a type safe data point of [T] in a motion test [TimeSeries]. 24 * 25 * [DataPoint]s include a [DataPointType] that specified how a value is written to / read from a 26 * JSON golden file. Additionally, some stand-in values such as [NotFoundDataPoint] and 27 * [NullDataPoint] allow to describe why [DataPoint] are absent in [TimeSeries], making the golden 28 * tests more robust. 29 */ 30 sealed interface DataPoint<out T> { 31 asJsonnull32 fun asJson(): Any 33 34 companion object { 35 fun <T> of(value: T?, type: DataPointType<T>): DataPoint<T> { 36 return if (value != null) { 37 ValueDataPoint(type.ensureImmutable(value), type) 38 } else { 39 nullValue() 40 } 41 } 42 43 fun <T> notFound(): DataPoint<T> { 44 @Suppress("UNCHECKED_CAST") return NotFoundDataPoint.instance as NotFoundDataPoint<T> 45 } 46 fun <T> nullValue(): DataPoint<T> { 47 @Suppress("UNCHECKED_CAST") return NullDataPoint.instance as NullDataPoint<T> 48 } 49 50 fun <T> unknownType(): DataPoint<T> { 51 @Suppress("UNCHECKED_CAST") return UnknownType.instance as UnknownType<T> 52 } 53 } 54 } 55 56 /** 57 * Wraps a non-`null` data point value. 58 * 59 * @see DataPoint.of 60 */ 61 data class ValueDataPoint<T> internal constructor(val value: T & Any, val type: DataPointType<T>) : <lambda>null62 DataPoint<T> { 63 override fun asJson() = type.toJson(this.value) 64 65 override fun toString(): String = "$value (${type.typeName})" 66 } 67 68 /** 69 * [DataPoint] stand-in to represent `null` data point values. 70 * 71 * @see DataPoint.of 72 * @see DataPoint.nullValue 73 */ 74 class NullDataPoint<T> private constructor() : DataPoint<T> { 75 asJsonnull76 override fun asJson() = JSONObject.NULL 77 78 companion object { 79 internal val instance = NullDataPoint<Any>() 80 81 fun isNullValue(jsonValue: Any): Boolean { 82 return jsonValue == JSONObject.NULL 83 } 84 } 85 toStringnull86 override fun toString(): String = "null" 87 } 88 89 /** 90 * [DataPoint] stand-in to represent data points that could not be sampled. 91 * 92 * This is usually the case when the subject to sample from did not exist in a specific frame in the 93 * first place. 94 * 95 * @see DataPoint.notFound 96 */ 97 class NotFoundDataPoint<T> private constructor() : DataPoint<T> { 98 99 override fun asJson() = JSONObject().apply { put("type", "not_found") } 100 101 override fun toString(): String = "{{not_found}}" 102 103 companion object { 104 internal val instance = NotFoundDataPoint<Any>() 105 106 fun isNotFoundValue(jsonValue: Any): Boolean { 107 return jsonValue is JSONObject && 108 jsonValue.has("type") && 109 jsonValue.getString("type") == "not_found" 110 } 111 } 112 } 113 114 /** [DataPoint] type indicating that a values was not readable during de-serialization. */ 115 class UnknownType<T> private constructor() : DataPoint<T> { 116 asJsonnull117 override fun asJson() = throw JSONException("Feature must not contain UnknownDataPoints") 118 119 override fun toString(): String = "{{unknown_type}}" 120 121 companion object { 122 internal val instance = UnknownType<Any>() 123 } 124 } 125