api-content-tracing-spec.ts 5.5 KB

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