api-content-tracing-spec.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { app, contentTracing, TraceConfig, TraceCategoriesAndOptions } from 'electron/main';
  2. import { expect } from 'chai';
  3. import * as fs from 'node:fs';
  4. import * as path from 'node:path';
  5. import { setTimeout } from 'node:timers/promises';
  6. import { ifdescribe } from './lib/spec-helpers';
  7. // FIXME: The tests are skipped on linux arm/arm64
  8. ifdescribe(!(['arm', 'arm64'].includes(process.arch)) || (process.platform !== 'linux'))('contentTracing', () => {
  9. const record = async (options: TraceConfig | TraceCategoriesAndOptions, outputFilePath: string | undefined, recordTimeInMilliseconds = 1e1) => {
  10. await app.whenReady();
  11. await contentTracing.startRecording(options);
  12. await setTimeout(recordTimeInMilliseconds);
  13. const resultFilePath = await contentTracing.stopRecording(outputFilePath);
  14. return resultFilePath;
  15. };
  16. const outputFilePath = path.join(app.getPath('temp'), 'trace.json');
  17. beforeEach(() => {
  18. if (fs.existsSync(outputFilePath)) {
  19. fs.unlinkSync(outputFilePath);
  20. }
  21. });
  22. describe('startRecording', function () {
  23. if (process.platform === 'win32' && process.arch === 'arm64') {
  24. // WOA needs more time
  25. this.timeout(10e3);
  26. } else {
  27. this.timeout(5e3);
  28. }
  29. const getFileSizeInKiloBytes = (filePath: string) => {
  30. const stats = fs.statSync(filePath);
  31. const fileSizeInBytes = stats.size;
  32. const fileSizeInKiloBytes = fileSizeInBytes / 1024;
  33. return fileSizeInKiloBytes;
  34. };
  35. it('accepts an empty config', async () => {
  36. const config = {};
  37. await record(config, outputFilePath);
  38. expect(fs.existsSync(outputFilePath)).to.be.true('output exists');
  39. const fileSizeInKiloBytes = getFileSizeInKiloBytes(outputFilePath);
  40. expect(fileSizeInKiloBytes).to.be.above(0,
  41. `the trace output file is empty, check "${outputFilePath}"`);
  42. });
  43. it('accepts a trace config', async () => {
  44. // (alexeykuzmin): All categories are excluded on purpose,
  45. // so only metadata gets into the output file.
  46. const config = {
  47. excluded_categories: ['*']
  48. };
  49. await record(config, outputFilePath);
  50. // If the `excluded_categories` param above is not respected, categories
  51. // like `node,node.environment` will be included in the output.
  52. const content = fs.readFileSync(outputFilePath).toString();
  53. expect(content.includes('"cat":"node,node.environment"')).to.be.false();
  54. });
  55. it('accepts "categoryFilter" and "traceOptions" as a config', async () => {
  56. // (alexeykuzmin): All categories are excluded on purpose,
  57. // so only metadata gets into the output file.
  58. const config = {
  59. categoryFilter: '__ThisIsANonexistentCategory__',
  60. traceOptions: ''
  61. };
  62. await record(config, outputFilePath);
  63. expect(fs.existsSync(outputFilePath)).to.be.true('output exists');
  64. // If the `categoryFilter` param above is not respected
  65. // the file size will be above 60KB.
  66. const fileSizeInKiloBytes = getFileSizeInKiloBytes(outputFilePath);
  67. const expectedMaximumFileSize = 60; // Depends on a platform.
  68. expect(fileSizeInKiloBytes).to.be.above(0,
  69. `the trace output file is empty, check "${outputFilePath}"`);
  70. expect(fileSizeInKiloBytes).to.be.below(expectedMaximumFileSize,
  71. `the trace output file is suspiciously large (${fileSizeInKiloBytes}KB),
  72. check "${outputFilePath}"`);
  73. });
  74. });
  75. ifdescribe(process.platform !== 'linux')('stopRecording', function () {
  76. if (process.platform === 'win32' && process.arch === 'arm64') {
  77. // WOA needs more time
  78. this.timeout(10e3);
  79. } else {
  80. this.timeout(5e3);
  81. }
  82. // FIXME(samuelmaddock): this test regularly flakes
  83. it.skip('does not crash on empty string', async () => {
  84. const options = {
  85. categoryFilter: '*',
  86. traceOptions: 'record-until-full,enable-sampling'
  87. };
  88. await contentTracing.startRecording(options);
  89. const path = await contentTracing.stopRecording('');
  90. expect(path).to.be.a('string').that.is.not.empty('result path');
  91. expect(fs.statSync(path).isFile()).to.be.true('output exists');
  92. });
  93. it('calls its callback with a result file path', async () => {
  94. const resultFilePath = await record(/* options */ {}, outputFilePath);
  95. expect(resultFilePath).to.be.a('string').and.be.equal(outputFilePath);
  96. });
  97. it('creates a temporary file when an empty string is passed', async function () {
  98. const resultFilePath = await record(/* options */ {}, /* outputFilePath */ '');
  99. expect(resultFilePath).to.be.a('string').that.is.not.empty('result path');
  100. });
  101. it('creates a temporary file when no path is passed', async function () {
  102. const resultFilePath = await record(/* options */ {}, /* outputFilePath */ undefined);
  103. expect(resultFilePath).to.be.a('string').that.is.not.empty('result path');
  104. });
  105. it('rejects if no trace is happening', async () => {
  106. await expect(contentTracing.stopRecording()).to.be.rejectedWith('Failed to stop tracing - no trace in progress');
  107. });
  108. });
  109. describe('captured events', () => {
  110. it('include V8 samples from the main process', async function () {
  111. this.timeout(60000);
  112. await contentTracing.startRecording({
  113. categoryFilter: 'disabled-by-default-v8.cpu_profiler',
  114. traceOptions: 'record-until-full'
  115. });
  116. {
  117. const start = Date.now();
  118. let n = 0;
  119. const f = () => {};
  120. while (Date.now() - start < 200 && n < 500) {
  121. await setTimeout(0);
  122. f();
  123. n++;
  124. }
  125. }
  126. const path = await contentTracing.stopRecording();
  127. const data = fs.readFileSync(path, 'utf8');
  128. const parsed = JSON.parse(data);
  129. expect(parsed.traceEvents.some((x: any) => x.cat === 'disabled-by-default-v8.cpu_profiler' && x.name === 'ProfileChunk')).to.be.true();
  130. });
  131. });
  132. });