>From Michael Blow <[email protected]>:
Michael Blow has uploaded this change for review. (
https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21186?usp=email )
Change subject: [NO ISSUE][HYR][MISC] Fix Span lifefycle / API issues
......................................................................
[NO ISSUE][HYR][MISC] Fix Span lifefycle / API issues
Ext-ref: MB-71012
Change-Id: I34f8fcf03329ea292c6b2637274c1c1c7c4f1462
---
M
hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
A
hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
A
hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
3 files changed, 618 insertions(+), 5 deletions(-)
git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb
refs/changes/86/21186/1
diff --git
a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
index 1a40360..e244f6e 100644
---
a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
+++
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
@@ -18,10 +18,17 @@
*/
package org.apache.hyracks.util;
+import static
org.apache.hyracks.util.annotations.AiProvenance.Agent.CLAUDE_SONNET_4_6;
+import static
org.apache.hyracks.util.annotations.AiProvenance.ContributionKind.REFACTORED;
+import static
org.apache.hyracks.util.annotations.AiProvenance.Tool.GITHUB_COPILOT;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import org.apache.hyracks.util.annotations.AiProvenance;
+
+@AiProvenance(agent = CLAUDE_SONNET_4_6, tool = GITHUB_COPILOT,
contributionKind = REFACTORED, notes = "Renamed init() to startElapsed(), made
ELAPSED an immutable sentinel, fixed elapsed() >= off-by-one, added
startElapsed() with do/while Javadoc")
public class Span {
public static final Span INFINITE = new Span() {
@@ -82,7 +89,62 @@
}
};
- public static final Span ELAPSED = start(0, TimeUnit.NANOSECONDS);
+ public static final Span ELAPSED = new Span() {
+ @Override
+ public void reset() {
+ // no-op
+ }
+
+ @Override
+ public long getSpanNanos() {
+ return 0;
+ }
+
+ @Override
+ public long getSpan(TimeUnit unit) {
+ return 0;
+ }
+
+ @Override
+ public boolean elapsed() {
+ return true;
+ }
+
+ @Override
+ public long elapsed(TimeUnit unit) {
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public void sleep() {
+ // no-op: already elapsed
+ }
+
+ @Override
+ public void sleep(long sleep, TimeUnit unit) {
+ // no-op: already elapsed
+ }
+
+ @Override
+ public long remaining(TimeUnit unit) {
+ return 0;
+ }
+
+ @Override
+ public void wait(Object monitor) {
+ // no-op: already elapsed
+ }
+
+ @Override
+ public boolean await(CountDownLatch latch) {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "<ELAPSED>";
+ }
+ };
private final long spanNanos;
private volatile long startNanos;
@@ -93,7 +155,6 @@
private Span(long span, TimeUnit unit) {
spanNanos = unit.toNanos(span);
- reset();
}
public void reset() {
@@ -109,11 +170,37 @@
}
public static Span start(long span, TimeUnit unit) {
- return new Span(span, unit);
+ Span s = new Span(span, unit);
+ s.reset();
+ return s;
+ }
+
+ /**
+ * Creates a span that is immediately elapsed, analogous to a do/while
loop: the elapsed condition is true on the
+ * first check, triggering immediate action, after which {@link #reset()}
begins the countdown for subsequent
+ * iterations. Contrast with {@link #start(long, TimeUnit)}, which is
analogous to a while/do loop: the span must
+ * actually expire before the elapsed condition becomes true.
+ * <p>
+ * Typical usage: bootstrapping state that has a TTL, where the first
fetch should happen immediately and
+ * subsequent refreshes should be rate-limited by the span duration.
+ * <pre>
+ * Span refreshSpan = Span.startElapsed(ttlSeconds, TimeUnit.SECONDS);
+ * while (running) {
+ * if (refreshSpan.elapsed()) {
+ * refresh();
+ * refreshSpan.reset();
+ * }
+ * }
+ * </pre>
+ */
+ public static Span startElapsed(long span, TimeUnit unit) {
+ Span s = new Span(span, unit);
+ s.startNanos = System.nanoTime() - s.spanNanos;
+ return s;
}
public boolean elapsed() {
- return elapsed(TimeUnit.NANOSECONDS) > spanNanos;
+ return elapsed(TimeUnit.NANOSECONDS) >= spanNanos;
}
public long elapsed(TimeUnit unit) {
@@ -196,7 +283,7 @@
if (micros > 0) {
builder.append(micros).append("us");
}
- if (nanos > 0 || builder.length() == 0) {
+ if (nanos > 0 || builder.isEmpty()) {
builder.append(nanos).append("ns");
}
return builder.toString();
diff --git
a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
new file mode 100644
index 0000000..33e6cfb
--- /dev/null
+++
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
@@ -0,0 +1,396 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.util.annotations;
+
+import static org.apache.hyracks.util.annotations.AiProvenance.AiProvenances;
+import static
org.apache.hyracks.util.annotations.AiProvenance.Agent.CLAUDE_SONNET_4_6;
+import static org.apache.hyracks.util.annotations.AiProvenance.Agent.GPT_5_3;
+import static
org.apache.hyracks.util.annotations.AiProvenance.Agent.GPT_5_MINI;
+import static
org.apache.hyracks.util.annotations.AiProvenance.ContributionKind.GENERATED;
+import static
org.apache.hyracks.util.annotations.AiProvenance.ContributionKind.REFACTORED;
+import static org.apache.hyracks.util.annotations.AiProvenance.Provider.*;
+import static org.apache.hyracks.util.annotations.AiProvenance.Tool.CHATGPT_UI;
+import static
org.apache.hyracks.util.annotations.AiProvenance.Tool.GITHUB_COPILOT;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to record AI provenance metadata for a program element.
+ *
+ * <p>This annotation may be applied to types, methods, constructors and fields
+ * to indicate that the element was generated or assisted by an AI model. It
+ * records which {@link Agent} (model), which {@link Tool} (invocation
+ * surface) and the kind of contribution via {@link ContributionKind}.</p>
+ *
+ * <p>The annotation is repeatable using the {@link AiProvenances} container
+ * which allows multiple provenance records to be attached to the same
+ * program element (for example: initial draft generated by one model and
+ * later refinement by another).</p>
+ *
+ * <p>Example:</p>
+ * <pre>
+ * import static org.apache.hyracks.util.annotations.AiProvenance.Agent.*;
+ * import static org.apache.hyracks.util.annotations.AiProvenance.Tool.*;
+ * import static
org.apache.hyracks.util.annotations.AiProvenance.ContributionKind.*;
+ *
+ * {@literal @}AiProvenance(agent = GPT_5_MINI, tool = OPENAI_API,
contributionKind = GENERATED,
+ * notes = "Initial PoC helper generated by GPT-5 Mini")
+ * public class ExampleGeneratedClass { ... }
+ * </pre>
+ *
+ */
+@AiProvenance(agent = GPT_5_3, tool = CHATGPT_UI, contributionKind =
GENERATED, notes = "Initial implementation generated via GPT-5.3 (browser)")
+@AiProvenance(agent = GPT_5_MINI, tool = GITHUB_COPILOT, contributionKind =
GENERATED, notes = "Refinements / Javadocs generated via GPT-5 Mini (GitHub
Copilot)")
+@Retention(RetentionPolicy.SOURCE)
+@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR,
ElementType.FIELD })
+@Repeatable(AiProvenances.class)
+public @interface AiProvenance {
+ Agent agent();
+
+ Tool tool();
+
+ ContributionKind contributionKind() default ContributionKind.GENERATED;
+
+ String notes() default "";
+
+ enum ContributionKind {
+ /**
+ * The element was fully generated by an AI model.
+ */
+ GENERATED,
+
+ /**
+ * The element was assisted or suggested by an AI model and requires
+ * human review or modification.
+ */
+ ASSISTED,
+
+ /**
+ * The element was refactored or rewritten by an AI model (not newly
+ * generated from scratch).
+ */
+ REFACTORED,
+
+ /**
+ * The element is a test that was generated by an AI model.
+ */
+ TEST_GENERATED,
+
+ /**
+ * The element is documentation that was generated by an AI model.
+ */
+ DOC_GENERATED
+ }
+
+ @AiProvenance(agent = CLAUDE_SONNET_4_6, tool = GITHUB_COPILOT,
contributionKind = REFACTORED)
+ enum Provider {
+ OPENAI("openai", "OpenAI"),
+ ANTHROPIC("anthropic", "Anthropic"),
+ GOOGLE("google", "Google"),
+ META("meta", "Meta"),
+ MISTRAL("mistral", "Mistral"),
+ COHERE("cohere", "Cohere"),
+ XAI("xai", "xAI"),
+ DEEPSEEK("deepseek", "DeepSeek"),
+ ALIBABA("alibaba", "Alibaba"),
+ MICROSOFT("microsoft", "Microsoft"),
+ GITHUB("github", "GitHub"),
+ JETBRAINS("jetbrains", "JetBrains"),
+ FACTORY("factory", "Factory.ai"),
+ LANGCHAIN("langchain", "LangChain"),
+ LLAMAINDEX("llamaindex", "LlamaIndex"),
+ PERPLEXITY("perplexity", "Perplexity"),
+ CURSOR("cursor", "Cursor"),
+ WINDSURF("windsurf", "Windsurf"),
+ TABNINE("tabnine", "Tabnine"),
+ CODEIUM("codeium", "Codeium"),
+ AMAZON("amazon", "Amazon"),
+ CUSTOM("custom", "Custom"),
+ GENERIC("generic", "Generic"),
+ OTHER("other", "Other");
+
+ private final String id;
+ private final String displayName;
+
+ Provider(String id, String displayName) {
+ this.id = id;
+ this.displayName = displayName;
+ }
+
+ /** Returns the canonical provider identifier (e.g. "openai",
"anthropic"). */
+ public String id() {
+ return id;
+ }
+
+ /** Human-friendly display name for the */
+ public String displayName() {
+ return displayName;
+ }
+ }
+
+ enum Agent {
+ // OpenAI
+ GPT_5_5(OPENAI, "gpt-5.5", "GPT-5.5"),
+ GPT_5_4(OPENAI, "gpt-5.4", "GPT-5.4"),
+ GPT_5_4_MINI(OPENAI, "gpt-5.4-mini", "GPT-5.4 Mini"),
+ GPT_5_4_NANO(OPENAI, "gpt-5.4-nano1", "GPT-5.4 Nano"),
+ GPT_5_3(OPENAI, "gpt-5.3", "GPT-5.3"),
+ GPT_5_2(OPENAI, "gpt-5.2", "GPT-5.2"),
+ GPT_5_MINI(OPENAI, "gpt-5-mini", "GPT-5 Mini"),
+
+ // =========================
+ // OpenAI — Codex
+ // =========================
+ GPT_5_1_CODEX(OPENAI, "gpt-5.1-codex", "GPT-5.1 Codex"),
+ GPT_5_2_CODEX(OPENAI, "gpt-5.2-codex", "GPT-5.2 Codex"),
+ GPT_5_3_CODEX(OPENAI, "gpt-5.3-codex", "GPT-5.3 Codex"),
+
+ // =========================
+ // OpenAI — GPT-4.x Family
+ // =========================
+ GPT_4_1(OPENAI, "gpt-4.1", "GPT-4.1"),
+ GPT_4_1_MINI(OPENAI, "gpt-4.1-mini", "GPT-4.1 Mini"),
+ GPT_4_1_NANO(OPENAI, "gpt-4.1-nano", "GPT-4.1 Nano"),
+
+ GPT_4O(OPENAI, "gpt-4o", "GPT-4o"),
+ GPT_4O_MINI(OPENAI, "gpt-4o-mini", "GPT-4o Mini"),
+
+ // =========================
+ // OpenAI — Reasoning (o-series)
+ // =========================
+ O1(OPENAI, "o1", "o1"),
+ O1_MINI(OPENAI, "o1-mini", "o1 Mini"),
+
+ O3(OPENAI, "o3", "o3"),
+ O3_MINI(OPENAI, "o3-mini", "o3 Mini"),
+
+ O4(OPENAI, "o4", "o4"),
+ O4_MINI(OPENAI, "o4-mini", "o4 Mini"),
+
+ // =========================
+ // Anthropic — Claude Opus
+ // =========================
+ CLAUDE_OPUS_4(ANTHROPIC, "claude-4-opus", "Claude Opus 4"),
+ CLAUDE_OPUS_4_7(ANTHROPIC, "claude-4-opus-4.7", "Claude Opus 4.7"),
+ CLAUDE_OPUS_4_6(ANTHROPIC, "claude-4-opus-4.6", "Claude Opus 4.6"),
+ CLAUDE_OPUS_4_6_FAST(ANTHROPIC, "claude-4-opus-4.6-fast", "Claude Opus
4.6 Fast"),
+ CLAUDE_OPUS_4_5(ANTHROPIC, "claude-4-opus-4.5", "Claude Opus 4.5"),
+
+ CLAUDE_OPUS_3(ANTHROPIC, "claude-3-opus", "Claude Opus 3"),
+
+ // =========================
+ // Anthropic — Claude Sonnet
+ // =========================
+ CLAUDE_SONNET_4(ANTHROPIC, "claude-4-sonnet", "Claude Sonnet 4"),
+ CLAUDE_SONNET_4_5(ANTHROPIC, "claude-4-sonnet-4.5", "Claude Sonnet
4.5"),
+ CLAUDE_SONNET_4_6(ANTHROPIC, "claude-4-sonnet-4.6", "Claude Sonnet
4.6"),
+
+ CLAUDE_SONNET_3(ANTHROPIC, "claude-3-sonnet", "Claude Sonnet 3"),
+ CLAUDE_SONNET_3_5(ANTHROPIC, "claude-3-5-sonnet", "Claude Sonnet 3.5"),
+
+ // =========================
+ // Anthropic — Claude Haiku
+ // =========================
+ CLAUDE_HAIKU_4(ANTHROPIC, "claude-4-haiku", "Claude Haiku 4"),
+ CLAUDE_HAIKU_4_5(ANTHROPIC, "claude-4-haiku-4.5", "Claude Haiku 4.5"),
+ CLAUDE_HAIKU_3(ANTHROPIC, "claude-3-haiku", "Claude Haiku 3"),
+
+ // =========================
+ // Google — Gemini
+ // =========================
+ GEMINI_3_1_PRO(GOOGLE, "gemini-3.1-pro", "Gemini 3.1 Pro"),
+ GEMINI_3_FLASH(GOOGLE, "gemini-3-flash", "Gemini 3 Flash"),
+ GEMINI_2_5_PRO(GOOGLE, "gemini-2.5-pro", "Gemini 2.5 Pro"),
+ GEMINI_1_5_PRO(GOOGLE, "gemini-1.5-pro", "Gemini 1.5 Pro"),
+ GEMINI_1_5_FLASH(GOOGLE, "gemini-1.5-flash", "Gemini 1.5 Flash"),
+
+ // =========================
+ // Meta
+ // =========================
+ LLAMA_3_70B(META, "llama-3-70b", "LLaMA 3 70B"),
+ LLAMA_3_8B(META, "llama-3-8b", "LLaMA 3 8B"),
+
+ // =========================
+ // Mistral
+ // =========================
+ MISTRAL_LARGE(MISTRAL, "mistral-large", "Mistral Large"),
+ MISTRAL_SMALL(MISTRAL, "mistral-small", "Mistral Small"),
+ MIXTRAL_8X7B(MISTRAL, "mixtral-8x7b", "Mixtral 8x7B"),
+
+ // =========================
+ // Cohere
+ // =========================
+ COMMAND_R_PLUS(COHERE, "command-r-plus", "Command R+"),
+ COMMAND_R(COHERE, "command-r", "Command R"),
+
+ // =========================
+ // xAI
+ // =========================
+ GROK_CODE_FAST_1(XAI, "grok-code-fast-1", "Grok Code Fast 1"),
+
+ // =========================
+ // OSS / local
+ // =========================
+ DEEPSEEK_CHAT(DEEPSEEK, "deepseek-chat", "DeepSeek Chat"),
+ DEEPSEEK_CODER(DEEPSEEK, "deepseek-coder", "DeepSeek Coder"),
+ QWEN_2(ALIBABA, "qwen-2", "Qwen 2"),
+ PHI_3(MICROSOFT, "phi-3", "Phi-3"),
+
+ // =========================
+ // Fine-tuned / tool-specific
+ // =========================
+ RAPTOR_MINI(GITHUB, "raptor-mini", "Raptor Mini"),
+ GOLDENEYE(GITHUB, "goldeneye", "Goldeneye"),
+
+ // =========================
+ // Droid Core / OSS-backed
+ // =========================
+ DROID_CORE_MINIMAX_M2_7(FACTORY, "droid-core-minimax-m2.7", "Droid
Core (MiniMax M2.7)"),
+ DROID_CORE_GLM_5_1(FACTORY, "droid-core-glm-5.1", "Droid Core
(GLM-5.1)"),
+ DROID_CORE_GLM_5(FACTORY, "droid-core-glm-5", "Droid Core (GLM-5)"),
+ DROID_CORE_KIMI_K2_6(FACTORY, "droid-core-kimi-k2.6", "Droid Core
(Kimi K2.6)"),
+ DROID_CORE_KIMI_K2_5(FACTORY, "droid-core-kimi-k2.5", "Droid Core
(Kimi K2.5)"),
+
+ // Fallback
+ OTHER(Provider.OTHER, "other", "Other");
+
+ private final Provider provider;
+ private final String modelId;
+ private final String displayName;
+
+ Agent(Provider provider, String modelId, String displayName) {
+ this.provider = provider;
+ this.modelId = modelId;
+ this.displayName = displayName;
+ }
+
+ /** Returns the {@link Provider} for this agent. */
+ public Provider provider() {
+ return provider;
+ }
+
+ /**
+ * Returns a canonical model identifier useful for telemetry and
+ * analytics (for example "gpt-5.4" or "claude-4-opus-4.6").
+ */
+ public String modelId() {
+ return modelId;
+ }
+
+ /** Human-friendly display name for the model. */
+ public String displayName() {
+ return displayName;
+ }
+ }
+
+ /**
+ * Container annotation for repeatable {@link AiProvenance} entries.
+ *
+ * <p>Kept with RUNTIME retention to allow tools that read compiled class
+ * files to discover provenance entries. The primary {@link AiProvenance}
+ * annotation is SOURCE-retained; the container is provided to satisfy the
+ * repeatable contract when tools choose to materialize the annotations at
+ * runtime.</p>
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR,
ElementType.FIELD })
+ @interface AiProvenances {
+ /**
+ * The contained provenance entries.
+ */
+ AiProvenance[] value();
+ }
+
+ enum Tool {
+
+ // Web / Chat UIs
+ CHATGPT_UI(OPENAI, "chatgpt-ui", "ChatGPT"),
+ CLAUDE_UI(ANTHROPIC, "claude-ui", "Claude UI"),
+ GEMINI_UI(GOOGLE, "gemini-ui", "Gemini UI"),
+ PERPLEXITY(Provider.PERPLEXITY, "perplexity", "Perplexity"),
+
+ // IDE integrations
+ GITHUB_COPILOT(GITHUB, "copilot", "GitHub Copilot"),
+ GEMINI_CODE_ASSIST(GOOGLE, "gemini-code-assist", "Gemini Code Assist"),
+ CURSOR(Provider.CURSOR, "cursor", "Cursor"),
+ WINDSURF(Provider.WINDSURF, "windsurf", "Windsurf"),
+ INTELLIJ_AI_ASSISTANT(JETBRAINS, "ai-assistant", "JetBrains AI
Assistant"),
+ VSCODE_AI_EXTENSION(MICROSOFT, "vscode-ai", "VS Code AI Extension"),
+
+ // APIs / SDK usage
+ OPENAI_API(OPENAI, "api", "OpenAI API"),
+ ANTHROPIC_API(ANTHROPIC, "api", "Anthropic API"),
+ GOOGLE_AI_API(GOOGLE, "api", "Google AI API"),
+ GENERIC_API(GENERIC, "api", "Generic API"),
+
+ // Agent / orchestration platforms
+ FACTORY(Provider.FACTORY, "factory-ai", "Factory.ai"),
+ LANGCHAIN(Provider.LANGCHAIN, "langchain", "LangChain"),
+ LLAMAINDEX(Provider.LLAMAINDEX, "llamaindex", "LlamaIndex"),
+ CUSTOM_AGENT(CUSTOM, "custom-agent", "Custom Agent Runtime"),
+
+ // CLI tools
+ OPENAI_CLI(OPENAI, "cli", "OpenAI CLI"),
+ ANTHROPIC_CLI(ANTHROPIC, "cli", "Anthropic CLI"),
+ FACTORY_CLI(Provider.FACTORY, "factory-cli", "Factory.ai CLI"),
+
+ // Fallback
+ OTHER(Provider.OTHER, "other", "Other");
+
+ private final Provider provider;
+ private final String id;
+ private final String displayName;
+
+ Tool(Provider provider, String id, String displayName) {
+ this.provider = provider;
+ this.id = id;
+ this.displayName = displayName;
+ }
+
+ /** Returns the {@link Provider} for this tool. */
+ public Provider provider() {
+ return provider;
+ }
+
+ /**
+ * Returns an identifier for the specific tool or integration
+ * (for example "api", "chatgpt-ui" or "copilot").
+ */
+ public String id() {
+ return id;
+ }
+
+ /** Human-friendly display name for the tool. */
+ public String displayName() {
+ return displayName;
+ }
+
+ /**
+ * Returns a compact qualified name composed of provider and id which
+ * is useful for logging and tagging (for example
"factory/factory-cli").
+ */
+ public String qualifiedName() {
+ return id() + "/" + id;
+ }
+ }
+}
diff --git
a/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
b/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
new file mode 100644
index 0000000..04ccf58
--- /dev/null
+++
b/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hyracks.util;
+
+import static
org.apache.hyracks.util.annotations.AiProvenance.Agent.CLAUDE_SONNET_4_6;
+import static
org.apache.hyracks.util.annotations.AiProvenance.ContributionKind.TEST_GENERATED;
+import static
org.apache.hyracks.util.annotations.AiProvenance.Tool.GITHUB_COPILOT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.hyracks.util.annotations.AiProvenance;
+import org.junit.Test;
+
+@AiProvenance(agent = CLAUDE_SONNET_4_6, tool = GITHUB_COPILOT,
contributionKind = TEST_GENERATED, notes = "Tests for startElapsed(), immutable
ELAPSED sentinel, and elapsed() >= boundary fix")
+public class SpanTest {
+
+ // --- Span.ELAPSED ---
+
+ @Test
+ public void elapsedConstant_alwaysElapsed() {
+ assertTrue(Span.ELAPSED.elapsed());
+ }
+
+ @Test
+ public void elapsedConstant_resetIsNoOp() {
+ Span.ELAPSED.reset();
+ assertTrue("ELAPSED.reset() should be a no-op; span should still be
elapsed", Span.ELAPSED.elapsed());
+ }
+
+ @Test
+ public void elapsedConstant_remainingIsZero() {
+ assertEquals(0, Span.ELAPSED.remaining(TimeUnit.NANOSECONDS));
+ assertEquals(0, Span.ELAPSED.remaining(TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void elapsedConstant_elapsedUnitIsMaxValue() {
+ assertEquals(Long.MAX_VALUE,
Span.ELAPSED.elapsed(TimeUnit.NANOSECONDS));
+ }
+
+ @Test
+ public void elapsedConstant_awaitReturnsFalseImmediately() throws
InterruptedException {
+ CountDownLatch latch = new CountDownLatch(1);
+ assertFalse(Span.ELAPSED.await(latch));
+ }
+
+ @Test
+ public void elapsedConstant_toStringIsElapsed() {
+ assertEquals("<ELAPSED>", Span.ELAPSED.toString());
+ }
+
+ // --- Span.startElapsed() ---
+
+ @Test
+ public void startElapsed_immediatelyElapsed() {
+ Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+ assertTrue("startElapsed span should immediately report elapsed()",
span.elapsed());
+ }
+
+ @Test
+ public void startElapsed_notElapsedAfterReset() {
+ Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+ span.reset();
+ assertFalse("startElapsed span should not be elapsed immediately after
reset()", span.elapsed());
+ }
+
+ @Test
+ public void startElapsed_elapsedAfterSpanDuration() throws
InterruptedException {
+ Span span = Span.startElapsed(10, TimeUnit.MILLISECONDS);
+ span.reset();
+ assertFalse(span.elapsed());
+ TimeUnit.MILLISECONDS.sleep(15);
+ assertTrue("startElapsed span should be elapsed after duration has
passed", span.elapsed());
+ }
+
+ @Test
+ public void startElapsed_remainingIsZeroBeforeReset() {
+ Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+ assertEquals(0, span.remaining(TimeUnit.NANOSECONDS));
+ }
+
+ @Test
+ public void startElapsed_remainingIsPositiveAfterReset() {
+ Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+ span.reset();
+ assertTrue("remaining should be positive after reset",
span.remaining(TimeUnit.MILLISECONDS) > 0);
+ }
+
+ // --- elapsed() boundary: >= ensures span is elapsed at exactly spanNanos
---
+
+ @Test
+ public void start_elapsedAtExactlySpanDuration() {
+ // A span of 0ns should be immediately elapsed (>= 0)
+ Span span = Span.start(0, TimeUnit.NANOSECONDS);
+ assertTrue("0ns span should be elapsed immediately (elapsed >=
spanNanos)", span.elapsed());
+ }
+
+ // --- Span.INFINITE sanity ---
+
+ @Test
+ public void infiniteConstant_neverElapsed() {
+ assertFalse(Span.INFINITE.elapsed());
+ }
+
+ @Test
+ public void infiniteConstant_remainingIsMaxValue() {
+ assertEquals(Long.MAX_VALUE,
Span.INFINITE.remaining(TimeUnit.NANOSECONDS));
+ }
+}
--
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21186?usp=email
To unsubscribe, or for help writing mail filters, visit
https://asterix-gerrit.ics.uci.edu/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: asterixdb
Gerrit-Branch: trinity
Gerrit-Change-Id: I34f8fcf03329ea292c6b2637274c1c1c7c4f1462
Gerrit-Change-Number: 21186
Gerrit-PatchSet: 1
Gerrit-Owner: Michael Blow <[email protected]>