>From Michael Blow <[email protected]>: Michael Blow has submitted this change. ( 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 - Fix elapsed() to use >= instead of >, which caused spans to require spanNanos + 1ns before reporting as elapsed (off-by-one bug) - Introduce startElapsed() which initializes startNanos so the span is immediately elapsed. startElapsed() is analogous to a do/while loop: elapsed on first check, then reset() begins the countdown; start() is while/do - Make ELAPSED an immutable sentinel (like INFINITE) with reset() as no-op, preventing accidental mutation of the shared constant - Use Span.start() (not startElapsed()) for genuine countdowns where pre-expiry would be dangerous Ext-ref: MB-71012 Change-Id: I34f8fcf03329ea292c6b2637274c1c1c7c4f1462 Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21186 Reviewed-by: Hussain Towaileb <[email protected]> Reviewed-by: Michael Blow <[email protected]> Tested-by: Jenkins <[email protected]> --- 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(-) Approvals: Hussain Towaileb: Looks good to me, approved Michael Blow: Looks good to me, but someone else must approve Jenkins: Verified 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: merged Gerrit-Project: asterixdb Gerrit-Branch: trinity Gerrit-Change-Id: I34f8fcf03329ea292c6b2637274c1c1c7c4f1462 Gerrit-Change-Number: 21186 Gerrit-PatchSet: 3 Gerrit-Owner: Michael Blow <[email protected]> Gerrit-Reviewer: Hussain Towaileb <[email protected]> Gerrit-Reviewer: Ian Maxon <[email protected]> Gerrit-Reviewer: Jenkins <[email protected]> Gerrit-Reviewer: Michael Blow <[email protected]> Gerrit-Reviewer: Ritik Raj <[email protected]> Gerrit-CC: Anon. E. Moose #1000171
