1/*
2 * Copyright (C) 2017 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
18package com.android.settings.search;
19
20import static com.google.common.truth.Truth.assertThat;
21import static org.mockito.ArgumentMatchers.nullable;
22import static org.mockito.Matchers.any;
23import static org.mockito.Matchers.anyInt;
24import static org.mockito.Matchers.anyString;
25import static org.mockito.Matchers.argThat;
26import static org.mockito.Matchers.eq;
27import static org.mockito.Mockito.mock;
28import static org.mockito.Mockito.never;
29import static org.mockito.Mockito.spy;
30import static org.mockito.Mockito.times;
31import static org.mockito.Mockito.verify;
32import static org.mockito.Mockito.when;
33
34import android.app.LoaderManager;
35import android.content.Context;
36import android.content.Intent;
37import android.content.Loader;
38import android.os.Bundle;
39import android.util.Pair;
40import android.view.View;
41
42import com.android.internal.logging.nano.MetricsProto;
43import com.android.settings.R;
44import com.android.settings.SettingsActivity;
45import com.android.settings.TestConfig;
46import com.android.settings.testutils.DatabaseTestUtils;
47import com.android.settings.testutils.FakeFeatureFactory;
48import com.android.settings.testutils.SettingsRobolectricTestRunner;
49import com.android.settings.testutils.shadow.SettingsShadowResources;
50
51import org.junit.After;
52import org.junit.Before;
53import org.junit.Test;
54import org.junit.runner.RunWith;
55import org.mockito.Answers;
56import org.mockito.ArgumentCaptor;
57import org.mockito.ArgumentMatcher;
58import org.mockito.Captor;
59import org.mockito.Mock;
60import org.mockito.MockitoAnnotations;
61import org.robolectric.Robolectric;
62import org.robolectric.RuntimeEnvironment;
63import org.robolectric.annotation.Config;
64import org.robolectric.util.ActivityController;
65import org.robolectric.util.ReflectionHelpers;
66
67import java.util.Set;
68
69@RunWith(SettingsRobolectricTestRunner.class)
70@Config(manifest = TestConfig.MANIFEST_PATH,
71        sdk = TestConfig.SDK_VERSION,
72        shadows = {
73                SettingsShadowResources.class,
74                SettingsShadowResources.SettingsShadowTheme.class,
75        })
76public class SearchFragmentTest {
77
78    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
79    private Context mContext;
80    @Mock
81    private DatabaseResultLoader mDatabaseResultLoader;
82    @Mock
83    private InstalledAppResultLoader mInstalledAppResultLoader;
84    @Mock
85    private AccessibilityServiceResultLoader mAccessibilityServiceResultLoader;
86    @Mock
87    private InputDeviceResultLoader mInputDeviceResultLoader;
88
89    @Mock
90    private SavedQueryLoader mSavedQueryLoader;
91    @Mock
92    private SavedQueryController mSavedQueryController;
93    @Mock
94    private SearchResultsAdapter mSearchResultsAdapter;
95    @Captor
96    private ArgumentCaptor<String> mQueryCaptor = ArgumentCaptor.forClass(String.class);
97
98    private FakeFeatureFactory mFeatureFactory;
99
100    @Before
101    public void setUp() {
102        MockitoAnnotations.initMocks(this);
103
104        mFeatureFactory = FakeFeatureFactory.setupForTest(mContext);
105    }
106
107    @After
108    public void tearDown() {
109        DatabaseTestUtils.clearDb(RuntimeEnvironment.application);
110    }
111
112    @Test
113    public void screenRotate_shouldPersistQuery() {
114        when(mFeatureFactory.searchFeatureProvider
115                .getDatabaseSearchLoader(any(Context.class), anyString()))
116                .thenReturn(mDatabaseResultLoader);
117        when(mFeatureFactory.searchFeatureProvider
118                .getInstalledAppSearchLoader(any(Context.class), anyString()))
119                .thenReturn(mInstalledAppResultLoader);
120        when(mFeatureFactory.searchFeatureProvider
121                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
122                .thenReturn(mAccessibilityServiceResultLoader);
123        when(mFeatureFactory.searchFeatureProvider
124                .getInputDeviceResultLoader(any(Context.class), anyString()))
125                .thenReturn(mInputDeviceResultLoader);
126        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
127                .thenReturn(mSavedQueryLoader);
128
129        final Bundle bundle = new Bundle();
130        final String testQuery = "test";
131        ActivityController<SearchActivity> activityController =
132                Robolectric.buildActivity(SearchActivity.class);
133        activityController.setup();
134        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
135                .findFragmentById(R.id.main_content);
136
137        ReflectionHelpers.setField(fragment, "mShowingSavedQuery", false);
138        fragment.mQuery = testQuery;
139
140        activityController.saveInstanceState(bundle).pause().stop().destroy();
141
142        activityController = Robolectric.buildActivity(SearchActivity.class);
143        activityController.setup(bundle);
144
145        assertThat(fragment.mQuery).isEqualTo(testQuery);
146    }
147
148    @Test
149    public void screenRotateEmptyString_ShouldNotCrash() {
150        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
151                .thenReturn(mSavedQueryLoader);
152
153        final Bundle bundle = new Bundle();
154        ActivityController<SearchActivity> activityController =
155                Robolectric.buildActivity(SearchActivity.class);
156        activityController.setup();
157        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
158                .findFragmentById(R.id.main_content);
159        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
160                .thenReturn(true);
161
162        fragment.mQuery = "";
163
164        activityController.saveInstanceState(bundle).pause().stop().destroy();
165
166        activityController = Robolectric.buildActivity(SearchActivity.class);
167        activityController.setup(bundle);
168
169        verify(mFeatureFactory.searchFeatureProvider, never())
170                .getDatabaseSearchLoader(any(Context.class), anyString());
171        verify(mFeatureFactory.searchFeatureProvider, never())
172                .getInstalledAppSearchLoader(any(Context.class), anyString());
173    }
174
175    @Test
176    public void queryTextChange_shouldTriggerLoaderAndInitializeSearch() {
177        when(mFeatureFactory.searchFeatureProvider
178                .getDatabaseSearchLoader(any(Context.class), anyString()))
179                .thenReturn(mDatabaseResultLoader);
180        when(mFeatureFactory.searchFeatureProvider
181                .getInstalledAppSearchLoader(any(Context.class), anyString()))
182                .thenReturn(mInstalledAppResultLoader);
183        when(mFeatureFactory.searchFeatureProvider
184                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
185                .thenReturn(mAccessibilityServiceResultLoader);
186        when(mFeatureFactory.searchFeatureProvider
187                .getInputDeviceResultLoader(any(Context.class), anyString()))
188                .thenReturn(mInputDeviceResultLoader);
189        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
190                .thenReturn(mSavedQueryLoader);
191
192        final String testQuery = "test";
193        ActivityController<SearchActivity> activityController =
194                Robolectric.buildActivity(SearchActivity.class);
195        activityController.setup();
196        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
197                .findFragmentById(R.id.main_content);
198        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
199                .thenReturn(true);
200
201        ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
202        fragment.onQueryTextChange(testQuery);
203        activityController.get().onBackPressed();
204
205        activityController.pause().stop().destroy();
206
207        verify(mFeatureFactory.metricsFeatureProvider, never()).action(
208                any(Context.class),
209                eq(MetricsProto.MetricsEvent.ACTION_LEAVE_SEARCH_RESULT_WITHOUT_QUERY));
210        verify(mFeatureFactory.metricsFeatureProvider).histogram(
211                any(Context.class), eq(SearchFragment.RESULT_CLICK_COUNT), eq(0));
212        verify(mFeatureFactory.searchFeatureProvider)
213                .getDatabaseSearchLoader(any(Context.class), anyString());
214        verify(mFeatureFactory.searchFeatureProvider)
215                .getInstalledAppSearchLoader(any(Context.class), anyString());
216        verify(mSearchResultsAdapter).initializeSearch(mQueryCaptor.capture());
217        assertThat(mQueryCaptor.getValue()).isEqualTo(testQuery);
218    }
219
220    @Test
221    public void onSearchResultsDisplayed_noResult_shouldShowNoResultView() {
222        ActivityController<SearchActivity> activityController =
223                Robolectric.buildActivity(SearchActivity.class);
224        activityController.setup();
225        SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager()
226                .findFragmentById(R.id.main_content));
227        fragment.onSearchResultsDisplayed(0 /* count */);
228
229        assertThat(fragment.mNoResultsView.getVisibility()).isEqualTo(View.VISIBLE);
230        verify(mFeatureFactory.metricsFeatureProvider).visible(
231                any(Context.class),
232                anyInt(),
233                eq(MetricsProto.MetricsEvent.SETTINGS_SEARCH_NO_RESULT));
234    }
235
236    @Test
237    public void queryTextChangeToEmpty_shouldLoadSavedQueryAndNotInitializeSearch() {
238        when(mFeatureFactory.searchFeatureProvider
239                .getDatabaseSearchLoader(any(Context.class), anyString()))
240                .thenReturn(mDatabaseResultLoader);
241        when(mFeatureFactory.searchFeatureProvider
242                .getInstalledAppSearchLoader(any(Context.class), anyString()))
243                .thenReturn(mInstalledAppResultLoader);
244        when(mFeatureFactory.searchFeatureProvider
245                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
246                .thenReturn(mAccessibilityServiceResultLoader);
247        when(mFeatureFactory.searchFeatureProvider
248                .getInputDeviceResultLoader(any(Context.class), anyString()))
249                .thenReturn(mInputDeviceResultLoader);
250        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
251                .thenReturn(mSavedQueryLoader);
252        ActivityController<SearchActivity> activityController =
253                Robolectric.buildActivity(SearchActivity.class);
254        activityController.setup();
255        SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager()
256                .findFragmentById(R.id.main_content));
257        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
258                .thenReturn(true);
259        ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
260        ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
261        fragment.mQuery = "123";
262
263        fragment.onQueryTextChange("");
264
265        verify(mFeatureFactory.searchFeatureProvider, never())
266                .getDatabaseSearchLoader(any(Context.class), anyString());
267        verify(mFeatureFactory.searchFeatureProvider, never())
268                .getInstalledAppSearchLoader(any(Context.class), anyString());
269        verify(mSavedQueryController).loadSavedQueries();
270        verify(mSearchResultsAdapter, never()).initializeSearch(anyString());
271    }
272
273    @Test
274    public void updateIndex_TriggerOnCreate() {
275        when(mFeatureFactory.searchFeatureProvider
276                .getDatabaseSearchLoader(any(Context.class), anyString()))
277                .thenReturn(mDatabaseResultLoader);
278        when(mFeatureFactory.searchFeatureProvider
279                .getInstalledAppSearchLoader(any(Context.class), anyString()))
280                .thenReturn(mInstalledAppResultLoader);
281        when(mFeatureFactory.searchFeatureProvider
282                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
283                .thenReturn(mAccessibilityServiceResultLoader);
284        when(mFeatureFactory.searchFeatureProvider
285                .getInputDeviceResultLoader(any(Context.class), anyString()))
286                .thenReturn(mInputDeviceResultLoader);
287        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
288                .thenReturn(mSavedQueryLoader);
289
290        ActivityController<SearchActivity> activityController =
291                Robolectric.buildActivity(SearchActivity.class);
292        activityController.setup();
293        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
294                .findFragmentById(R.id.main_content);
295        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
296                .thenReturn(true);
297
298        fragment.onAttach(null);
299        verify(mFeatureFactory.searchFeatureProvider).updateIndexAsync(any(Context.class),
300                any(IndexingCallback.class));
301    }
302
303    @Test
304    public void syncLoaders_MergeWhenAllLoadersDone() {
305        when(mFeatureFactory.searchFeatureProvider
306                .getDatabaseSearchLoader(any(Context.class), anyString()))
307                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
308        when(mFeatureFactory.searchFeatureProvider
309                .getInstalledAppSearchLoader(any(Context.class), anyString()))
310                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
311        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
312                .thenReturn(mSavedQueryLoader);
313
314        ActivityController<SearchActivity> activityController =
315                Robolectric.buildActivity(SearchActivity.class);
316        activityController.setup();
317
318        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
319                .findFragmentById(R.id.main_content));
320        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
321                .thenReturn(true);
322
323        fragment.onQueryTextChange("non-empty");
324
325        Robolectric.flushForegroundThreadScheduler();
326
327        verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(Set.class));
328    }
329
330    @Test
331    public void whenNoQuery_HideFeedbackIsCalled() {
332        when(mFeatureFactory.searchFeatureProvider
333                .getDatabaseSearchLoader(any(Context.class), anyString()))
334                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
335        when(mFeatureFactory.searchFeatureProvider
336                .getInstalledAppSearchLoader(any(Context.class), anyString()))
337                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
338        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
339                .thenReturn(mSavedQueryLoader);
340
341        ActivityController<SearchActivity> activityController =
342                Robolectric.buildActivity(SearchActivity.class);
343        activityController.setup();
344        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
345                .findFragmentById(R.id.main_content));
346        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
347                .thenReturn(true);
348        when(fragment.getLoaderManager()).thenReturn(mock(LoaderManager.class));
349
350        fragment.onQueryTextChange("");
351        Robolectric.flushForegroundThreadScheduler();
352
353        verify(mFeatureFactory.searchFeatureProvider).hideFeedbackButton();
354    }
355
356    @Test
357    public void onLoadFinished_ShowsFeedback() {
358        when(mFeatureFactory.searchFeatureProvider
359                .getDatabaseSearchLoader(any(Context.class), anyString()))
360                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
361        when(mFeatureFactory.searchFeatureProvider
362                .getInstalledAppSearchLoader(any(Context.class), anyString()))
363                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
364        when(mFeatureFactory.searchFeatureProvider
365                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
366                .thenReturn(new MockAccessibilityLoader(RuntimeEnvironment.application));
367        when(mFeatureFactory.searchFeatureProvider
368                .getInputDeviceResultLoader(any(Context.class), anyString()))
369                .thenReturn(new MockInputDeviceResultLoader(RuntimeEnvironment.application));
370        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
371                .thenReturn(mSavedQueryLoader);
372        ActivityController<SearchActivity> activityController =
373                Robolectric.buildActivity(SearchActivity.class);
374        activityController.setup();
375        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
376                .findFragmentById(R.id.main_content);
377        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
378                .thenReturn(true);
379
380        fragment.onQueryTextChange("non-empty");
381        Robolectric.flushForegroundThreadScheduler();
382
383        verify(mFeatureFactory.searchFeatureProvider).showFeedbackButton(any(SearchFragment.class),
384                any(View.class));
385    }
386
387    @Test
388    public void preIndexingFinished_isIndexingFinishedFlag_isFalse() {
389        ActivityController<SearchActivity> activityController =
390                Robolectric.buildActivity(SearchActivity.class);
391        activityController.setup();
392        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
393                .findFragmentById(R.id.main_content);
394
395        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
396                .thenReturn(false);
397    }
398
399    @Test
400    public void onIndexingFinished_notShowingSavedQuery_initLoaders() {
401        ActivityController<SearchActivity> activityController =
402                Robolectric.buildActivity(SearchActivity.class);
403        activityController.setup();
404        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
405                .findFragmentById(R.id.main_content));
406        final LoaderManager loaderManager = mock(LoaderManager.class);
407        when(fragment.getLoaderManager()).thenReturn(loaderManager);
408        fragment.mShowingSavedQuery = false;
409        fragment.mQuery = null;
410
411        fragment.onIndexingFinished();
412
413        verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.DATABASE),
414                eq(null), any(LoaderManager.LoaderCallbacks.class));
415        verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.INSTALLED_APPS),
416                eq(null), any(LoaderManager.LoaderCallbacks.class));
417    }
418
419    @Test
420    public void onIndexingFinished_showingSavedQuery_loadsSavedQueries() {
421        ActivityController<SearchActivity> activityController =
422                Robolectric.buildActivity(SearchActivity.class);
423        activityController.setup();
424        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
425                .findFragmentById(R.id.main_content));
426        fragment.mShowingSavedQuery = true;
427        ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
428
429        fragment.onIndexingFinished();
430
431        verify(fragment.mSavedQueryController).loadSavedQueries();
432    }
433
434    @Test
435    public void onIndexingFinished_noActivity_shouldNotCrash() {
436        ActivityController<SearchActivity> activityController =
437                Robolectric.buildActivity(SearchActivity.class);
438        activityController.setup();
439        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
440                .findFragmentById(R.id.main_content));
441        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
442                .thenReturn(true);
443        fragment.mQuery = "bright";
444        ReflectionHelpers.setField(fragment, "mLoaderManager", null);
445        ReflectionHelpers.setField(fragment, "mHost", null);
446
447        fragment.onIndexingFinished();
448        // no crash
449    }
450
451    @Test
452    public void onSearchResultClicked_shouldLogResultMeta() {
453        SearchFragment fragment = new SearchFragment();
454        ReflectionHelpers.setField(fragment, "mMetricsFeatureProvider",
455                mFeatureFactory.metricsFeatureProvider);
456        ReflectionHelpers.setField(fragment, "mSearchFeatureProvider",
457                mFeatureFactory.searchFeatureProvider);
458        ReflectionHelpers.setField(fragment, "mSearchAdapter", mock(SearchResultsAdapter.class));
459        fragment.mSavedQueryController = mock(SavedQueryController.class);
460
461        // Should log result name, result count, clicked rank, etc.
462        final SearchViewHolder resultViewHolder = mock(SearchViewHolder.class);
463        when(resultViewHolder.getClickActionMetricName())
464                .thenReturn(MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT);
465        ResultPayload payLoad = new ResultPayload(
466                (new Intent()).putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, "test_setting"));
467        SearchResult searchResult = new SearchResult.Builder()
468                .setStableId(payLoad.hashCode())
469                .setPayload(payLoad)
470                .setTitle("setting_title")
471                .build();
472        fragment.onSearchResultClicked(resultViewHolder, searchResult);
473
474        verify(mFeatureFactory.metricsFeatureProvider).action(
475                nullable(Context.class),
476                eq(MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT),
477                eq("test_setting"),
478                argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_COUNT)),
479                argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK)),
480                argThat(pairMatches(MetricsProto.MetricsEvent
481                                .FIELD_SETTINGS_SEARCH_RESULT_ASYNC_RANKING_STATE)),
482                argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH)));
483
484        verify(mFeatureFactory.searchFeatureProvider).searchResultClicked(nullable(Context.class),
485                nullable(String.class), eq(searchResult));
486    }
487
488    @Test
489    public void onResume_shouldCallSearchRankingWarmupIfSmartSearchRankingEnabled(){
490        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
491                .thenReturn(true);
492
493        ActivityController<SearchActivity> activityController =
494                Robolectric.buildActivity(SearchActivity.class);
495        activityController.setup();
496        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
497                .findFragmentById(R.id.main_content);
498
499        verify(mFeatureFactory.searchFeatureProvider)
500                .searchRankingWarmup(any(Context.class));
501    }
502
503    @Test
504    public void onResume_shouldNotCallSearchRankingWarmupIfSmartSearchRankingDisabled(){
505        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
506                .thenReturn(false);
507
508        ActivityController<SearchActivity> activityController =
509                Robolectric.buildActivity(SearchActivity.class);
510        activityController.setup();
511        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
512                .findFragmentById(R.id.main_content);
513
514        verify(mFeatureFactory.searchFeatureProvider, never())
515                .searchRankingWarmup(any(Context.class));
516    }
517
518    private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag) {
519        return pair -> pair.first == tag;
520    }
521}
522