Skip to content

Commit

Permalink
[ftr] implement FtrService classes and migrate common services (elast…
Browse files Browse the repository at this point in the history
…ic#99546)

Co-authored-by: spalger <spalger@users.noreply.github.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
3 people authored and ecezalp committed May 26, 2021
1 parent 751d69e commit d291637
Show file tree
Hide file tree
Showing 20 changed files with 292 additions and 253 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,14 @@ export default function (/* { providerAPI } */) {
}
-----------

**Services**:::
Services are named singleton values produced by a Service Provider. Tests and other services can retrieve service instances by asking for them by name. All functionality except the mocha API is exposed via services.
**Service**:::
A Service is a named singleton created using a subclass of `FtrService`. Tests and other services can retrieve service instances by asking for them by name. All functionality except the mocha API is exposed via services. When you write your own functional tests check for existing services that help with the interactions you're looking to execute, and add new services for interactions which aren't already encoded in a service.

**Service Providers**:::
For legacy purposes, and for when creating a subclass of `FtrService` is inconvenient, you can also create services using a "Service Provider". These are functions which which create service instances and return them. These instances are cached and provided to tests. Currently these providers may also return a Promise for the service instance, allowing the service to do some setup work before tests run. We expect to fully deprecate and remove support for async service providers in the near future and instead require that services use the `lifecycle` service to run setup before tests. Providers which return instances of classes other than `FtrService` will likely remain supported for as long as possible.

**Page objects**:::
Page objects are a special type of service that encapsulate behaviors common to a particular page or plugin. When you write your own plugin, you’ll likely want to add a page object (or several) that describes the common interactions your tests need to execute.
Page objects are functionally equivalent to services, except they are loaded with a slightly different mechanism and generally defined separate from services. When you write your own functional tests you might want to write some of your services as Page objects, but it is not required.

**Test Files**:::
The `FunctionalTestRunner`'s primary purpose is to execute test files. These files export a Test Provider that is called with a Provider API but is not expected to return a value. Instead Test Providers define a suite using https://mochajs.org/#bdd[mocha's BDD interface].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { loadTracer } from '../load_tracer';
import { createAsyncInstance, isAsyncInstance } from './async_instance';
import { Providers } from './read_provider_spec';
import { createVerboseInstance } from './verbose_instance';
import { GenericFtrService } from '../../public_types';

export class ProviderCollection {
private readonly instances = new Map();
Expand Down Expand Up @@ -58,12 +59,19 @@ export class ProviderCollection {
}

public invokeProviderFn(provider: (args: any) => any) {
return provider({
const ctx = {
getService: this.getService,
hasService: this.hasService,
getPageObject: this.getPageObject,
getPageObjects: this.getPageObjects,
});
};

if (provider.prototype instanceof GenericFtrService) {
const Constructor = (provider as any) as new (ctx: any) => any;
return new Constructor(ctx);
}

return provider(ctx);
}

private findProvider(type: string, name: string) {
Expand Down
12 changes: 10 additions & 2 deletions packages/kbn-test/src/functional_test_runner/public_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Test, Suite } from './fake_mocha_types';

export { Lifecycle, Config, FailureMetadata };

interface AsyncInstance<T> {
export interface AsyncInstance<T> {
/**
* Services that are initialized async are not ready before the tests execute, so you might need
* to call `init()` and await the promise it returns before interacting with the service
Expand All @@ -39,7 +39,11 @@ export type ProvidedType<T extends (...args: any[]) => any> = MaybeAsyncInstance
* promise types into the async instances that other providers will receive.
*/
type ProvidedTypeMap<T extends {}> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? ProvidedType<T[K]> : unknown;
[K in keyof T]: T[K] extends new (...args: any[]) => infer X
? X
: T[K] extends (...args: any[]) => any
? ProvidedType<T[K]>
: unknown;
};

export interface GenericFtrProviderContext<
Expand Down Expand Up @@ -84,6 +88,10 @@ export interface GenericFtrProviderContext<
loadTestFile(path: string): void;
}

export class GenericFtrService<ProviderContext extends GenericFtrProviderContext<any, any>> {
constructor(protected readonly ctx: ProviderContext) {}
}

export interface FtrConfigProviderContext {
log: ToolingLog;
readConfigFile(path: string): Promise<Config>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
* Side Public License, v 1.
*/

import { GenericFtrProviderContext } from '@kbn/test';
import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test';

import { services } from './services';

export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
export class FtrService extends GenericFtrService<FtrProviderContext> {}
60 changes: 29 additions & 31 deletions test/common/services/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,37 @@ import { get } from 'lodash';
import fetch from 'node-fetch';
import { getUrl } from '@kbn/test';

import { FtrProviderContext } from '../ftr_provider_context';
import { FtrService } from '../ftr_provider_context';

export function DeploymentProvider({ getService }: FtrProviderContext) {
const config = getService('config');
export class DeploymentService extends FtrService {
private readonly config = this.ctx.getService('config');

return {
/**
* Returns Kibana host URL
*/
getHostPort() {
return getUrl.baseUrl(config.get('servers.kibana'));
},
/**
* Returns Kibana host URL
*/
getHostPort() {
return getUrl.baseUrl(this.config.get('servers.kibana'));
}

/**
* Returns ES host URL
*/
getEsHostPort() {
return getUrl.baseUrl(config.get('servers.elasticsearch'));
},
/**
* Returns ES host URL
*/
getEsHostPort() {
return getUrl.baseUrl(this.config.get('servers.elasticsearch'));
}

async isCloud(): Promise<boolean> {
const baseUrl = this.getHostPort();
const username = config.get('servers.kibana.username');
const password = config.get('servers.kibana.password');
const response = await fetch(baseUrl + '/api/stats?extended', {
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'),
},
});
const data = await response.json();
return get(data, 'usage.cloud.is_cloud_enabled', false);
},
};
async isCloud(): Promise<boolean> {
const baseUrl = this.getHostPort();
const username = this.config.get('servers.kibana.username');
const password = this.config.get('servers.kibana.password');
const response = await fetch(baseUrl + '/api/stats?extended', {
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'),
},
});
const data = await response.json();
return get(data, 'usage.cloud.is_cloud_enabled', false);
}
}
16 changes: 8 additions & 8 deletions test/common/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@
* Side Public License, v 1.
*/

import { DeploymentProvider } from './deployment';
import { DeploymentService } from './deployment';
import { LegacyEsProvider } from './legacy_es';
import { ElasticsearchProvider } from './elasticsearch';
import { EsArchiverProvider } from './es_archiver';
import { KibanaServerProvider } from './kibana_server';
import { RetryProvider } from './retry';
import { RandomnessProvider } from './randomness';
import { RetryService } from './retry';
import { RandomnessService } from './randomness';
import { SecurityServiceProvider } from './security';
import { EsDeleteAllIndicesProvider } from './es_delete_all_indices';
import { SavedObjectInfoProvider } from './saved_object_info';
import { SavedObjectInfoService } from './saved_object_info';

export const services = {
deployment: DeploymentProvider,
deployment: DeploymentService,
legacyEs: LegacyEsProvider,
es: ElasticsearchProvider,
esArchiver: EsArchiverProvider,
kibanaServer: KibanaServerProvider,
retry: RetryProvider,
randomness: RandomnessProvider,
retry: RetryService,
randomness: RandomnessService,
security: SecurityServiceProvider,
esDeleteAllIndices: EsDeleteAllIndicesProvider,
savedObjectInfo: SavedObjectInfoProvider,
savedObjectInfo: SavedObjectInfoService,
};
2 changes: 1 addition & 1 deletion test/common/services/kibana_server/kibana_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { KbnClient } from '@kbn/test';

import { FtrProviderContext } from '../../ftr_provider_context';

export function KibanaServerProvider({ getService }: FtrProviderContext) {
export function KibanaServerProvider({ getService }: FtrProviderContext): KbnClient {
const log = getService('log');
const config = getService('config');
const lifecycle = getService('lifecycle');
Expand Down
93 changes: 49 additions & 44 deletions test/common/services/randomness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@
*/

import Chance from 'chance';
import { ToolingLog } from '@kbn/dev-utils';

import { FtrProviderContext } from '../ftr_provider_context';
import { FtrService } from '../ftr_provider_context';

let __CACHED_SEED__: number | undefined;
function getSeed(log: ToolingLog) {
if (__CACHED_SEED__ !== undefined) {
return __CACHED_SEED__;
}

__CACHED_SEED__ = Date.now();
log.debug('randomness seed: %j', __CACHED_SEED__);
return __CACHED_SEED__;
}

interface CharOptions {
pool?: string;
Expand All @@ -27,52 +39,45 @@ interface NumberOptions {
max?: number;
}

export function RandomnessProvider({ getService }: FtrProviderContext) {
const log = getService('log');

const seed = Date.now();
log.debug('randomness seed: %j', seed);

const chance = new Chance(seed);
export class RandomnessService extends FtrService {
private readonly chance = new Chance(getSeed(this.ctx.getService('log')));

return new (class RandomnessService {
/**
* Generate a random natural number
*
* range: 0 to 9007199254740991
*
*/
naturalNumber(options?: NumberOptions) {
return chance.natural(options);
}
/**
* Generate a random natural number
*
* range: 0 to 9007199254740991
*
*/
naturalNumber(options?: NumberOptions) {
return this.chance.natural(options);
}

/**
* Generate a random integer
*/
integer(options?: NumberOptions) {
return chance.integer(options);
}
/**
* Generate a random integer
*/
integer(options?: NumberOptions) {
return this.chance.integer(options);
}

/**
* Generate a random number, defaults to at least 4 and no more than 8 syllables
*/
word(options: { syllables?: number } = {}) {
const { syllables = this.naturalNumber({ min: 4, max: 8 }) } = options;
/**
* Generate a random number, defaults to at least 4 and no more than 8 syllables
*/
word(options: { syllables?: number } = {}) {
const { syllables = this.naturalNumber({ min: 4, max: 8 }) } = options;

return chance.word({
syllables,
});
}
return this.chance.word({
syllables,
});
}

/**
* Generate a random string, defaults to at least 8 and no more than 15 alpha-numeric characters
*/
string(options: StringOptions = {}) {
return chance.string({
length: this.naturalNumber({ min: 8, max: 15 }),
...(options.pool === 'undefined' ? { alpha: true, numeric: true, symbols: false } : {}),
...options,
});
}
})();
/**
* Generate a random string, defaults to at least 8 and no more than 15 alpha-numeric characters
*/
string(options: StringOptions = {}) {
return this.chance.string({
length: this.naturalNumber({ min: 8, max: 15 }),
...(options.pool === 'undefined' ? { alpha: true, numeric: true, symbols: false } : {}),
...options,
});
}
}
2 changes: 1 addition & 1 deletion test/common/services/retry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
* Side Public License, v 1.
*/

export { RetryProvider } from './retry';
export { RetryService } from './retry';
Loading

0 comments on commit d291637

Please sign in to comment.