aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/test/java/com/yahoo/search/federation/FederationResultTest.java
blob: 71004f5a98af0d794c03efc4d99f70eae013dc1d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.federation;

import com.google.common.collect.ImmutableSet;
import com.yahoo.component.chain.Chain;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.search.searchchain.Execution;
import com.yahoo.search.searchchain.FutureResult;
import com.yahoo.search.searchchain.model.federation.FederationOptions;
import com.yahoo.test.ManualClock;
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author bratseth
 */
public class FederationResultTest {
    
    private static final FederationSearcher.Target organic = new MockTarget("organic", 500);
    private static final FederationSearcher.Target dsp1 = new MockTarget("dsp1", 240);
    private static final FederationSearcher.Target dsp2 = new MockTarget("dsp2", 200);

    private final ManualClock clock = new ManualClock();

    @Test
    void testFederationResult() {
        assertTimeout(ImmutableSet.of(),               100, 200, 180);
        assertTimeout(ImmutableSet.of(),               480, 400, 400);
        assertTimeout(ImmutableSet.of("dsp1"),         260, 280, 220);
        assertTimeout(ImmutableSet.of("organic"),      520, 160, 160);
        assertTimeout(ImmutableSet.of("dsp2"),         200, 220, 230);
        assertTimeout(ImmutableSet.of(),               200, 220, 210);
        assertTimeout(ImmutableSet.of("dsp1", "dsp2"), 200, 260, 260);
        assertTimeout(ImmutableSet.of("organic"),      520, 260, 260);
    }

    private void assertTimeout(Set<String> expectedTimeoutNames, int ... responseTimes) {
        FederationResult.Builder builder = new FederationResult.Builder();
        builder.add(organic, resultAfter(responseTimes[0]));
        builder.add(dsp1,    resultAfter(responseTimes[1]));
        builder.add(dsp2,    resultAfter(responseTimes[2]));
        FederationResult federationResult = builder.build();
        federationResult.waitForAll(50, clock);
        assertEquals(3, federationResult.all().size());
        for (FederationResult.TargetResult targetResult : federationResult.all()) {
            Result result = targetResult.getOrTimeoutError();
            if (expectedTimeoutNames.contains(targetResult.target.getId().toString()))
                assertTrue(timedOut(result), targetResult.target.getId() + " timed out");
            else
                assertFalse(timedOut(result), targetResult.target.getId() + " did not time out");
        }
    }
    
    private MockFutureResult resultAfter(int time) {
        return new MockFutureResult(new Query(), time);        
    }
    
    private boolean timedOut(Result result) {
        ErrorMessage error = result.hits().getError();
        if (error == null) return false;
        return error.getCode() == ErrorMessage.timeoutCode;
    }

    private class MockFutureResult extends FutureResult {
        
        private final int responseTime;
        private final Query query;
        private final long startTime;
        
        MockFutureResult(Query query, int responseTime) {
            super(() -> new Result(query), new Execution(Execution.Context.createContextStub()), query);
            this.responseTime = responseTime;
            this.query = query;
            startTime = clock.millis();
        }

        @Override
        public Result get() { throw new RuntimeException(); }

        @Override
        public Optional<Result> getIfAvailable(long timeout, TimeUnit timeunit) {
            if (timeunit != TimeUnit.MILLISECONDS) throw new RuntimeException();

            long elapsedTime = clock.millis() - startTime;
            long leftUntilResponse = responseTime - elapsedTime;
            if (leftUntilResponse > timeout) {
                clock.advance(Duration.ofMillis(timeout));
                return Optional.empty();
            }
            else {
                if (leftUntilResponse > 0) // otherwise we already spent more time than this sources timeout
                    clock.advance(Duration.ofMillis(leftUntilResponse));
                return Optional.of(new Result(query));
            }
        }
        
        @Override
        public Query getQuery() {
            return query;
        }

    }
    
    private static class MockTarget extends FederationSearcher.Target {

        private final Chain<Searcher> chain;
        private final int timeout;
        
        MockTarget(String id, int timeout) {
            this.chain = new Chain<>(id);
            this.timeout = timeout;
        }

        @Override
        Chain<Searcher> getChain() { return chain; }

        @Override
        void modifyTargetQuery(Query query) { }

        @Override
        void modifyTargetResult(Result result) { }

        @Override
        public FederationOptions federationOptions() {
            return new FederationOptions(false, timeout, true);
        }

    }
    
}