mirror of
https://github.com/eclipse-threadx/filex.git
synced 2026-09-14 04:05:59 +08:00
Compare commits
4
Commits
v6.5.1.202602_rel
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a17b07bf5 | ||
|
|
b3c653c0c5 | ||
|
|
9da40d41d7 | ||
|
|
7cbe022bab |
+1
-1
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
|
|||||||
|
|
||||||
# Set up the project
|
# Set up the project
|
||||||
project(filex
|
project(filex
|
||||||
LANGUAGES C ASM
|
LANGUAGES C
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+30
-11
@@ -1,20 +1,39 @@
|
|||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
|
This Eclipse Foundation Project adheres to the [Eclipse Foundation Vulnerability Reporting Policy](https://www.eclipse.org/security/policy/).
|
||||||
|
|
||||||
|
## How To Report a Vulnerability
|
||||||
|
|
||||||
|
If you think you have found a vulnerability in this repository, please report it to us through coordinated disclosure.
|
||||||
|
|
||||||
|
**Please do not report security vulnerabilities through public issues, discussions, or change requests.**
|
||||||
|
|
||||||
|
Instead, [report it privately here](https://github.com/eclipse-threadx/filex/security/advisories/new) on GitHub
|
||||||
|
|
||||||
|
You can find more information about reporting and disclosure at the [Eclipse Foundation Security page](https://www.eclipse.org/security/).
|
||||||
|
|
||||||
|
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
|
||||||
|
|
||||||
|
* The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting)
|
||||||
|
* Affected version(s)
|
||||||
|
* Impact of the issue, including how an attacker might exploit the issue
|
||||||
|
* Step-by-step instructions to reproduce the issue
|
||||||
|
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||||
|
* Full paths of source file(s) related to the manifestation of the issue
|
||||||
|
* Configuration required to reproduce the issue
|
||||||
|
* Log files that are related to this issue (if possible)
|
||||||
|
* Proof-of-concept or exploit code (if possible)
|
||||||
|
|
||||||
|
This information will help us triage your report more quickly.
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 6.4.x | :white_check_mark: |
|
| 6.5.x | :white_check_mark: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
Eclipse ThreadX publishes a release every quarter. There are no long-term support branches or backports to older releases.
|
||||||
|
|
||||||
If you think you have found a vulnerability in <project> you can report it using one of the following ways:
|
Normally, fixes for non-critical vulnerabilities will ship in a regularly scheduled quarterly release. If fixes for an urgent or critical vulnerability must ship quickly, then the project will publish a hotfix release of the affected component(s) without waiting for the next quarterly release.
|
||||||
|
|
||||||
* Contact the [Eclipse Foundation Security Team](mailto:security@eclipse-foundation.org)
|
You can learn more about the [project's release cadence in this blog post](https://blogs.eclipse.org/post/fr%C3%A9d%C3%A9ric-desbiens/eclipse-threadx-more-predictable-more-open).
|
||||||
* [Report a Vulnerability](https://github.com/eclipse-threadx/filex/security/advisories/new)
|
|
||||||
|
|
||||||
You can find more information about reporting and disclosure at the [Eclipse Foundation Security page](https://www.eclipse.org/security/).
|
|
||||||
|
|
||||||
## Security Policy
|
|
||||||
|
|
||||||
This project follows [Eclipse Foundation Vulnerability Reporting Policy](https://www.eclipse.org/security/policy/).
|
|
||||||
|
|||||||
@@ -29,6 +29,17 @@
|
|||||||
#include "fx_directory.h"
|
#include "fx_directory.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* DEPRECATION NOTICE
|
||||||
|
* fx_directory_long_name_get() is deprecated. Do not use it in new code.
|
||||||
|
*
|
||||||
|
* WHY: Does not accept a destination buffer length; writes up to FX_MAX_LONG_NAME_LEN bytes regardless of the caller's buffer size, risking a buffer overrun.
|
||||||
|
*
|
||||||
|
* WHAT TO DO: replace calls with fx_directory_long_name_get_extended(), passing the actual
|
||||||
|
* destination buffer size as an additional argument.
|
||||||
|
*/
|
||||||
|
#pragma message("fx_directory_long_name_get() is deprecated. " \
|
||||||
|
"Use fx_directory_long_name_get_extended() and pass the actual buffer size.")
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* */
|
/* */
|
||||||
/* FUNCTION RELEASE */
|
/* FUNCTION RELEASE */
|
||||||
@@ -45,7 +56,8 @@
|
|||||||
/* name. If there is no long file name, the short file name will be */
|
/* name. If there is no long file name, the short file name will be */
|
||||||
/* returned. */
|
/* returned. */
|
||||||
/* */
|
/* */
|
||||||
/* Note, this API is deprecated as fx_directory_long_name_get_extended */
|
/* DEPRECATED. Use fx_directory_long_name_get_extended() instead, passing the actual
|
||||||
|
destination buffer length. Does not accept a destination buffer length; writes up to FX_MAX_LONG_NAME_LEN bytes regardless of the caller's buffer size, risking a buffer overrun.
|
||||||
/* should be used. The maximum written size to long_file_name could be */
|
/* should be used. The maximum written size to long_file_name could be */
|
||||||
/* FX_MAX_LONG_NAME_LEN. */
|
/* FX_MAX_LONG_NAME_LEN. */
|
||||||
/* */
|
/* */
|
||||||
|
|||||||
@@ -29,6 +29,17 @@
|
|||||||
#include "fx_directory.h"
|
#include "fx_directory.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* DEPRECATION NOTICE
|
||||||
|
* fx_directory_short_name_get() is deprecated. Do not use it in new code.
|
||||||
|
*
|
||||||
|
* WHY: Does not accept a destination buffer length; writes up to FX_MAX_SHORT_NAME_LEN bytes regardless of the caller's buffer size, risking a buffer overrun.
|
||||||
|
*
|
||||||
|
* WHAT TO DO: replace calls with fx_directory_short_name_get_extended(), passing the actual
|
||||||
|
* destination buffer size as an additional argument.
|
||||||
|
*/
|
||||||
|
#pragma message("fx_directory_short_name_get() is deprecated. " \
|
||||||
|
"Use fx_directory_short_name_get_extended() and pass the actual buffer size.")
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* */
|
/* */
|
||||||
/* FUNCTION RELEASE */
|
/* FUNCTION RELEASE */
|
||||||
@@ -45,7 +56,8 @@
|
|||||||
/* name. If the long file name is really a short file name, the short */
|
/* name. If the long file name is really a short file name, the short */
|
||||||
/* file name will be returned. */
|
/* file name will be returned. */
|
||||||
/* */
|
/* */
|
||||||
/* Note, this API is deprecated as fx_directory_short_name_get_extended*/
|
/* DEPRECATED. Use fx_directory_short_name_get_extended() instead, passing the actual
|
||||||
|
destination buffer length. Does not accept a destination buffer length; writes up to FX_MAX_SHORT_NAME_LEN bytes regardless of the caller's buffer size, risking a buffer overrun.
|
||||||
/* should be used. The maximum written size to short_file_name could */
|
/* should be used. The maximum written size to short_file_name could */
|
||||||
/* be FX_MAX_SHORT_NAME_LEN. */
|
/* be FX_MAX_SHORT_NAME_LEN. */
|
||||||
/* */
|
/* */
|
||||||
|
|||||||
@@ -32,6 +32,17 @@
|
|||||||
#include "fx_utility.h"
|
#include "fx_utility.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* DEPRECATION NOTICE
|
||||||
|
* fx_media_volume_get() is deprecated. Do not use it in new code.
|
||||||
|
*
|
||||||
|
* WHY: Does not accept a volume-name buffer length; writes up to 12 bytes regardless of the caller's buffer size, risking a buffer overrun.
|
||||||
|
*
|
||||||
|
* WHAT TO DO: replace calls with fx_media_volume_get_extended(), passing the actual
|
||||||
|
* destination buffer size as an additional argument.
|
||||||
|
*/
|
||||||
|
#pragma message("fx_media_volume_get() is deprecated. " \
|
||||||
|
"Use fx_media_volume_get_extended() and pass the actual buffer size.")
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* */
|
/* */
|
||||||
/* FUNCTION RELEASE */
|
/* FUNCTION RELEASE */
|
||||||
@@ -47,7 +58,8 @@
|
|||||||
/* This function reads the volume name stored in the media's boot */
|
/* This function reads the volume name stored in the media's boot */
|
||||||
/* record or root directory. */
|
/* record or root directory. */
|
||||||
/* */
|
/* */
|
||||||
/* Note, this API is deprecated as fx_media_volume_get_extended should */
|
/* DEPRECATED. Use fx_media_volume_get_extended() instead, passing the actual
|
||||||
|
destination buffer length. Does not accept a volume-name buffer length; writes up to 12 bytes regardless of the caller's buffer size, risking a buffer overrun.
|
||||||
/* be used. The maximum written size to volume_name could be 12. */
|
/* be used. The maximum written size to volume_name could be 12. */
|
||||||
/* */
|
/* */
|
||||||
/* INPUT */
|
/* INPUT */
|
||||||
|
|||||||
@@ -29,6 +29,17 @@
|
|||||||
#include "fx_unicode.h"
|
#include "fx_unicode.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* DEPRECATION NOTICE
|
||||||
|
* fx_unicode_length_get() is deprecated. Do not use it in new code.
|
||||||
|
*
|
||||||
|
* WHY: Does not accept a buffer length; scans up to 256 bytes regardless of the actual buffer size, risking an overread.
|
||||||
|
*
|
||||||
|
* WHAT TO DO: replace calls with fx_unicode_length_get_extended(), passing the actual
|
||||||
|
* destination buffer size as an additional argument.
|
||||||
|
*/
|
||||||
|
#pragma message("fx_unicode_length_get() is deprecated. " \
|
||||||
|
"Use fx_unicode_length_get_extended() and pass the actual buffer size.")
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* */
|
/* */
|
||||||
/* FUNCTION RELEASE */
|
/* FUNCTION RELEASE */
|
||||||
@@ -43,7 +54,8 @@
|
|||||||
/* */
|
/* */
|
||||||
/* This function returns the length of the supplied unicode name. */
|
/* This function returns the length of the supplied unicode name. */
|
||||||
/* */
|
/* */
|
||||||
/* Note, this API is deprecated as _fx_unicode_length_get_extended */
|
/* DEPRECATED. Use fx_unicode_length_get_extended() instead, passing the actual
|
||||||
|
destination buffer length. Does not accept a buffer length; scans up to 256 bytes regardless of the actual buffer size, risking an overread.
|
||||||
/* should be used. The maximum buffer size of unicode_name is 256. */
|
/* should be used. The maximum buffer size of unicode_name is 256. */
|
||||||
/* */
|
/* */
|
||||||
/* INPUT */
|
/* INPUT */
|
||||||
|
|||||||
@@ -29,6 +29,17 @@
|
|||||||
#include "fx_unicode.h"
|
#include "fx_unicode.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* DEPRECATION NOTICE
|
||||||
|
* fx_unicode_name_get() is deprecated. Do not use it in new code.
|
||||||
|
*
|
||||||
|
* WHY: Does not accept a destination buffer length; writes up to FX_MAX_LONG_NAME_LEN*2 bytes regardless of the caller's buffer size, risking a buffer overrun.
|
||||||
|
*
|
||||||
|
* WHAT TO DO: replace calls with fx_unicode_name_get_extended(), passing the actual
|
||||||
|
* destination buffer size as an additional argument.
|
||||||
|
*/
|
||||||
|
#pragma message("fx_unicode_name_get() is deprecated. " \
|
||||||
|
"Use fx_unicode_name_get_extended() and pass the actual buffer size.")
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* */
|
/* */
|
||||||
/* FUNCTION RELEASE */
|
/* FUNCTION RELEASE */
|
||||||
@@ -44,7 +55,8 @@
|
|||||||
/* This function finds the unicode name associated with the supplied */
|
/* This function finds the unicode name associated with the supplied */
|
||||||
/* short 8.3 name. */
|
/* short 8.3 name. */
|
||||||
/* */
|
/* */
|
||||||
/* Note, this API is deprecated as fx_unicode_name_get_extended should */
|
/* DEPRECATED. Use fx_unicode_name_get_extended() instead, passing the actual
|
||||||
|
destination buffer length. Does not accept a destination buffer length; writes up to FX_MAX_LONG_NAME_LEN*2 bytes regardless of the caller's buffer size, risking a buffer overrun.
|
||||||
/* be used. The maximum written size to destination_unicode_name could */
|
/* be used. The maximum written size to destination_unicode_name could */
|
||||||
/* be FX_MAX_LONG_NAME_LEN * 2. */
|
/* be FX_MAX_LONG_NAME_LEN * 2. */
|
||||||
/* */
|
/* */
|
||||||
|
|||||||
@@ -29,6 +29,17 @@
|
|||||||
#include "fx_unicode.h"
|
#include "fx_unicode.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* DEPRECATION NOTICE
|
||||||
|
* fx_unicode_short_name_get() is deprecated. Do not use it in new code.
|
||||||
|
*
|
||||||
|
* WHY: Does not accept a destination buffer length; writes up to 13 bytes regardless of the caller's buffer size, risking a buffer overrun.
|
||||||
|
*
|
||||||
|
* WHAT TO DO: replace calls with fx_unicode_short_name_get_extended(), passing the actual
|
||||||
|
* destination buffer size as an additional argument.
|
||||||
|
*/
|
||||||
|
#pragma message("fx_unicode_short_name_get() is deprecated. " \
|
||||||
|
"Use fx_unicode_short_name_get_extended() and pass the actual buffer size.")
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* */
|
/* */
|
||||||
/* FUNCTION RELEASE */
|
/* FUNCTION RELEASE */
|
||||||
@@ -44,7 +55,8 @@
|
|||||||
/* This function finds the short name associated with the supplied */
|
/* This function finds the short name associated with the supplied */
|
||||||
/* unicode name. */
|
/* unicode name. */
|
||||||
/* */
|
/* */
|
||||||
/* Note, this API is deprecated as fx_unicode_short_name_get_extended */
|
/* DEPRECATED. Use fx_unicode_short_name_get_extended() instead, passing the actual
|
||||||
|
destination buffer length. Does not accept a destination buffer length; writes up to 13 bytes regardless of the caller's buffer size, risking a buffer overrun.
|
||||||
/* should be used. The maximum written size to destination_short_name */
|
/* should be used. The maximum written size to destination_short_name */
|
||||||
/* could be 13. */
|
/* could be 13. */
|
||||||
/* */
|
/* */
|
||||||
|
|||||||
@@ -306,8 +306,6 @@ UCHAR cache_found = FX_FALSE;
|
|||||||
return(FX_SECTOR_INVALID);
|
return(FX_SECTOR_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Just write the buffer to the media. */
|
|
||||||
|
|
||||||
#ifndef FX_MEDIA_STATISTICS_DISABLE
|
#ifndef FX_MEDIA_STATISTICS_DISABLE
|
||||||
|
|
||||||
/* Increment the number of driver write sector(s) requests. */
|
/* Increment the number of driver write sector(s) requests. */
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
target_sources(${PROJECT_NAME} PRIVATE
|
||||||
|
# {{BEGIN_TARGET_SOURCES}}
|
||||||
|
|
||||||
|
# {{END_TARGET_SOURCES}}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME}
|
||||||
|
PUBLIC
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/inc
|
||||||
|
)
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
* Copyright (C) 2026 Eclipse ThreadX contributors
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the MIT License which is available at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*
|
||||||
|
* AI Disclosure: This file was largely AI-generated by Copilot (Claude Sonnet 4.6).
|
||||||
|
* The AI-generated portions may be considered public domain (CC0-1.0)
|
||||||
|
* and not subject to the project's licence. The human contributor has
|
||||||
|
* reviewed and verified that the code is correct.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT and CC0-1.0
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/**************************************************************************/
|
||||||
|
/** */
|
||||||
|
/** FileX Component */
|
||||||
|
/** */
|
||||||
|
/** Port Specific */
|
||||||
|
/** */
|
||||||
|
/**************************************************************************/
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/* */
|
||||||
|
/* PORT SPECIFIC C INFORMATION RELEASE */
|
||||||
|
/* */
|
||||||
|
/* fx_port.h Win64/Visual */
|
||||||
|
/* 6.5.1.202602 */
|
||||||
|
/* */
|
||||||
|
/* AUTHOR */
|
||||||
|
/* */
|
||||||
|
/* Eclipse ThreadX contributors */
|
||||||
|
/* */
|
||||||
|
/* DESCRIPTION */
|
||||||
|
/* */
|
||||||
|
/* This file contains data type definitions that make the FileX FAT */
|
||||||
|
/* compatible file system function identically on a variety of */
|
||||||
|
/* different processor architectures. For example, the byte offset of */
|
||||||
|
/* various entries in the boot record, and directory entries are */
|
||||||
|
/* defined in this file. */
|
||||||
|
/* */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef FX_PORT_H
|
||||||
|
#define FX_PORT_H
|
||||||
|
|
||||||
|
|
||||||
|
/* Determine if the optional FileX user define file should be used. */
|
||||||
|
|
||||||
|
#ifdef FX_INCLUDE_USER_DEFINE_FILE
|
||||||
|
|
||||||
|
|
||||||
|
/* Yes, include the user defines in fx_user.h. The defines in this file may
|
||||||
|
alternately be defined on the command line. */
|
||||||
|
|
||||||
|
#include "fx_user.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
||||||
|
/* Include the ThreadX api file. */
|
||||||
|
|
||||||
|
#ifndef FX_STANDALONE_ENABLE
|
||||||
|
#include "tx_api.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* Define ULONG64 typedef, if not already defined. */
|
||||||
|
|
||||||
|
#ifndef ULONG64_DEFINED
|
||||||
|
#define ULONG64_DEFINED
|
||||||
|
typedef unsigned long long ULONG64;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* Define compiler library include files. */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifndef VOID
|
||||||
|
#define VOID void
|
||||||
|
typedef char CHAR;
|
||||||
|
typedef char BOOL;
|
||||||
|
typedef unsigned char UCHAR;
|
||||||
|
typedef int INT;
|
||||||
|
typedef unsigned int UINT;
|
||||||
|
typedef long LONG;
|
||||||
|
typedef unsigned long ULONG;
|
||||||
|
typedef short SHORT;
|
||||||
|
typedef unsigned short USHORT;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ULONG64_DEFINED
|
||||||
|
#define ULONG64_DEFINED
|
||||||
|
typedef unsigned long long ULONG64;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Define basic alignment type used in block and byte pool operations. This data type must
|
||||||
|
be at least 32-bits in size and also be large enough to hold a pointer type.
|
||||||
|
On Win64, pointers are 64 bits so ULONG (32-bit on Windows LLP64) is insufficient. */
|
||||||
|
|
||||||
|
#ifndef ALIGN_TYPE_DEFINED
|
||||||
|
#define ALIGN_TYPE_DEFINED
|
||||||
|
#define ALIGN_TYPE ULONG64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* FX_STANDALONE_ENABLE */
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef FX_REGRESSION_TEST
|
||||||
|
|
||||||
|
/* Define parameters for regression test suite. */
|
||||||
|
|
||||||
|
#define FX_MAX_SECTOR_CACHE 256
|
||||||
|
#define FX_MAX_FAT_CACHE 64
|
||||||
|
#define FX_FAT_MAP_SIZE 1
|
||||||
|
|
||||||
|
|
||||||
|
/* Define variables and macros used to introduce errors for the regression test suite. */
|
||||||
|
|
||||||
|
extern ULONG _fx_ram_driver_io_error_request;
|
||||||
|
extern ULONG _fx_ram_driver_io_request_count;
|
||||||
|
extern ULONG _fx_file_open_max_file_size_request;
|
||||||
|
extern ULONG _fx_directory_entry_read_count;
|
||||||
|
extern ULONG _fx_directory_entry_read_error_request;
|
||||||
|
extern ULONG _fx_directory_entry_write_count;
|
||||||
|
extern ULONG _fx_directory_entry_write_error_request;
|
||||||
|
extern ULONG _fx_utility_fat_entry_write_count;
|
||||||
|
extern ULONG _fx_utility_fat_entry_write_error_request;
|
||||||
|
extern ULONG _fx_utility_fat_entry_read_count;
|
||||||
|
extern ULONG _fx_utility_fat_entry_read_error_request;
|
||||||
|
extern ULONG _fx_utility_logical_sector_flush_count;
|
||||||
|
extern ULONG _fx_utility_logical_sector_flush_error_request;
|
||||||
|
extern ULONG _fx_utility_logical_sector_write_count;
|
||||||
|
extern ULONG _fx_utility_logical_sector_write_error_request;
|
||||||
|
extern ULONG _fx_utility_logical_sector_read_count;
|
||||||
|
extern ULONG _fx_utility_logical_sector_read_error_request;
|
||||||
|
extern ULONG _fx_utility_logical_sector_read_1_count;
|
||||||
|
extern ULONG _fx_utility_logical_sector_read_1_error_request;
|
||||||
|
|
||||||
|
#ifdef FX_ENABLE_FAULT_TOLERANT
|
||||||
|
struct FX_MEDIA_STRUCT;
|
||||||
|
extern VOID fault_tolerant_enable_callback(struct FX_MEDIA_STRUCT *media_ptr,
|
||||||
|
UCHAR *fault_tolerant_memory_buffer,
|
||||||
|
ULONG log_size);
|
||||||
|
extern VOID fault_tolerant_apply_log_callback(struct FX_MEDIA_STRUCT *media_ptr,
|
||||||
|
UCHAR *fault_tolerant_memory_buffer,
|
||||||
|
ULONG log_size);
|
||||||
|
#endif /* FX_ENABLE_FAULT_TOLERANT */
|
||||||
|
|
||||||
|
|
||||||
|
#define FX_DIRECTORY_ENTRY_READ_EXTENSION _fx_directory_entry_read_count++; \
|
||||||
|
if (_fx_directory_entry_read_error_request) \
|
||||||
|
{ \
|
||||||
|
_fx_directory_entry_read_error_request--; \
|
||||||
|
if (_fx_directory_entry_read_error_request == 0) \
|
||||||
|
{ \
|
||||||
|
return(FX_IO_ERROR); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FX_DIRECTORY_ENTRY_WRITE_EXTENSION _fx_directory_entry_write_count++; \
|
||||||
|
if (_fx_directory_entry_write_error_request) \
|
||||||
|
{ \
|
||||||
|
_fx_directory_entry_write_error_request--; \
|
||||||
|
if (_fx_directory_entry_write_error_request == 0) \
|
||||||
|
{ \
|
||||||
|
return(FX_IO_ERROR); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FX_UTILITY_FAT_ENTRY_READ_EXTENSION _fx_utility_fat_entry_read_count++; \
|
||||||
|
if (_fx_utility_fat_entry_read_error_request) \
|
||||||
|
{ \
|
||||||
|
_fx_utility_fat_entry_read_error_request--; \
|
||||||
|
if (_fx_utility_fat_entry_read_error_request == 0) \
|
||||||
|
{ \
|
||||||
|
return(FX_IO_ERROR); \
|
||||||
|
} \
|
||||||
|
if (_fx_utility_fat_entry_read_error_request == 10000) \
|
||||||
|
{ \
|
||||||
|
*entry_ptr = 1; \
|
||||||
|
_fx_utility_fat_entry_read_error_request = 0; \
|
||||||
|
return(FX_SUCCESS); \
|
||||||
|
} \
|
||||||
|
if (_fx_utility_fat_entry_read_error_request == 20000) \
|
||||||
|
{ \
|
||||||
|
*entry_ptr = media_ptr -> fx_media_fat_reserved; \
|
||||||
|
_fx_utility_fat_entry_read_error_request = 0; \
|
||||||
|
return(FX_SUCCESS); \
|
||||||
|
} \
|
||||||
|
if (_fx_utility_fat_entry_read_error_request == 30000) \
|
||||||
|
{ \
|
||||||
|
*entry_ptr = cluster; \
|
||||||
|
_fx_utility_fat_entry_read_error_request = 0; \
|
||||||
|
return(FX_SUCCESS); \
|
||||||
|
} \
|
||||||
|
if (_fx_utility_fat_entry_read_error_request == 40000) \
|
||||||
|
{ \
|
||||||
|
media_ptr -> fx_media_total_clusters = 0; \
|
||||||
|
_fx_utility_fat_entry_read_error_request = 0; \
|
||||||
|
return(FX_SUCCESS); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FX_UTILITY_FAT_ENTRY_WRITE_EXTENSION _fx_utility_fat_entry_write_count++; \
|
||||||
|
if (_fx_utility_fat_entry_write_error_request) \
|
||||||
|
{ \
|
||||||
|
_fx_utility_fat_entry_write_error_request--; \
|
||||||
|
if (_fx_utility_fat_entry_write_error_request == 0) \
|
||||||
|
{ \
|
||||||
|
return(FX_IO_ERROR); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FX_UTILITY_LOGICAL_SECTOR_FLUSH_EXTENSION _fx_utility_logical_sector_flush_count++; \
|
||||||
|
if (_fx_utility_logical_sector_flush_error_request) \
|
||||||
|
{ \
|
||||||
|
_fx_utility_logical_sector_flush_error_request--; \
|
||||||
|
if (_fx_utility_logical_sector_flush_error_request == 0) \
|
||||||
|
{ \
|
||||||
|
return(FX_IO_ERROR); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FX_UTILITY_LOGICAL_SECTOR_READ_EXTENSION _fx_utility_logical_sector_read_count++; \
|
||||||
|
if (_fx_utility_logical_sector_read_error_request) \
|
||||||
|
{ \
|
||||||
|
_fx_utility_logical_sector_read_error_request--; \
|
||||||
|
if (_fx_utility_logical_sector_read_error_request == 0) \
|
||||||
|
{ \
|
||||||
|
return(FX_IO_ERROR); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FX_UTILITY_LOGICAL_SECTOR_READ_EXTENSION_1 _fx_utility_logical_sector_read_1_count++; \
|
||||||
|
if (_fx_utility_logical_sector_read_1_error_request) \
|
||||||
|
{ \
|
||||||
|
_fx_utility_logical_sector_read_1_error_request--; \
|
||||||
|
if (_fx_utility_logical_sector_read_1_error_request == 0) \
|
||||||
|
{ \
|
||||||
|
cache_entry = FX_NULL; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FX_UTILITY_LOGICAL_SECTOR_WRITE_EXTENSION _fx_utility_logical_sector_write_count++; \
|
||||||
|
if (_fx_utility_logical_sector_write_error_request) \
|
||||||
|
{ \
|
||||||
|
_fx_utility_logical_sector_write_error_request--; \
|
||||||
|
if (_fx_utility_logical_sector_write_error_request == 0) \
|
||||||
|
{ \
|
||||||
|
return(FX_IO_ERROR); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FX_FAULT_TOLERANT_ENABLE_EXTENSION fault_tolerant_enable_callback(media_ptr, media_ptr -> fx_media_fault_tolerant_memory_buffer, total_size);
|
||||||
|
#define FX_FAULT_TOLERANT_APPLY_LOGS_EXTENSION fault_tolerant_apply_log_callback(media_ptr, media_ptr -> fx_media_fault_tolerant_memory_buffer, size);
|
||||||
|
|
||||||
|
#endif /* FX_REGRESSION_TEST */
|
||||||
|
|
||||||
|
|
||||||
|
/* Define FileX internal protection macros. If FX_SINGLE_THREAD is defined,
|
||||||
|
these protection macros are effectively disabled. However, for multi-thread
|
||||||
|
uses, the macros are setup to utilize a ThreadX mutex for multiple thread
|
||||||
|
access control into an open media. */
|
||||||
|
|
||||||
|
/* Reduce the mutex error checking for testing purpose. */
|
||||||
|
|
||||||
|
#if defined(FX_SINGLE_THREAD) || defined(FX_STANDALONE_ENABLE)
|
||||||
|
#define FX_PROTECT
|
||||||
|
#define FX_UNPROTECT
|
||||||
|
#else
|
||||||
|
#define FX_PROTECT tx_mutex_get(&(media_ptr -> fx_media_protect), TX_WAIT_FOREVER);
|
||||||
|
#define FX_UNPROTECT tx_mutex_put(&(media_ptr -> fx_media_protect));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Define interrupt lockout constructs to protect the system date/time from being updated
|
||||||
|
while they are being read. */
|
||||||
|
|
||||||
|
#ifndef FX_STANDALONE_ENABLE
|
||||||
|
#define FX_INT_SAVE_AREA unsigned int old_interrupt_posture;
|
||||||
|
#define FX_DISABLE_INTS old_interrupt_posture = tx_interrupt_control(TX_INT_DISABLE);
|
||||||
|
#define FX_RESTORE_INTS tx_interrupt_control(old_interrupt_posture);
|
||||||
|
#else
|
||||||
|
#ifndef FX_LEGACY_INTERRUPT_PROTECTION
|
||||||
|
#define FX_LEGACY_INTERRUPT_PROTECTION
|
||||||
|
#endif
|
||||||
|
#define FX_INT_SAVE_AREA
|
||||||
|
#define FX_DISABLE_INTS
|
||||||
|
#define FX_RESTORE_INTS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Define the error checking logic to determine if there is a caller error in the FileX API.
|
||||||
|
The default definitions assume ThreadX is being used. This code can be completely turned
|
||||||
|
off by just defining these macros to white space. */
|
||||||
|
|
||||||
|
#ifndef FX_STANDALONE_ENABLE
|
||||||
|
#ifndef TX_TIMER_PROCESS_IN_ISR
|
||||||
|
|
||||||
|
#define FX_CALLER_CHECKING_EXTERNS extern TX_THREAD *_tx_thread_current_ptr; \
|
||||||
|
extern TX_THREAD _tx_timer_thread; \
|
||||||
|
extern volatile ULONG _tx_thread_system_state;
|
||||||
|
|
||||||
|
#define FX_CALLER_CHECKING_CODE if ((_tx_thread_system_state) || \
|
||||||
|
(_tx_thread_current_ptr == TX_NULL) || \
|
||||||
|
(_tx_thread_current_ptr == &_tx_timer_thread)) \
|
||||||
|
return(FX_CALLER_ERROR);
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define FX_CALLER_CHECKING_EXTERNS extern TX_THREAD *_tx_thread_current_ptr; \
|
||||||
|
extern volatile ULONG _tx_thread_system_state;
|
||||||
|
|
||||||
|
#define FX_CALLER_CHECKING_CODE if ((_tx_thread_system_state) || \
|
||||||
|
(_tx_thread_current_ptr == TX_NULL)) \
|
||||||
|
return(FX_CALLER_ERROR);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#define FX_CALLER_CHECKING_EXTERNS
|
||||||
|
#define FX_CALLER_CHECKING_CODE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Define the update rate of the system timer. These values may also be defined at the command
|
||||||
|
line when compiling the fx_system_initialize.c module in the FileX library build. Alternatively,
|
||||||
|
they can be modified in this file or fx_user.h. Note: the update rate must be an even number of
|
||||||
|
seconds greater than or equal to 2, which is the minimal update rate for FAT time. */
|
||||||
|
|
||||||
|
/* Define the number of seconds the timer parameters are updated in FileX. The default
|
||||||
|
value is 10 seconds. This value can be overwritten externally. */
|
||||||
|
|
||||||
|
#ifndef FX_UPDATE_RATE_IN_SECONDS
|
||||||
|
#define FX_UPDATE_RATE_IN_SECONDS 10
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Defines the number of ThreadX timer ticks required to achieve the update rate specified by
|
||||||
|
FX_UPDATE_RATE_IN_SECONDS defined previously. By default, the ThreadX timer tick is 10ms,
|
||||||
|
so the default value for this constant is 1000. If TX_TIMER_TICKS_PER_SECOND is defined,
|
||||||
|
this value is derived from TX_TIMER_TICKS_PER_SECOND. */
|
||||||
|
|
||||||
|
#ifndef FX_UPDATE_RATE_IN_TICKS
|
||||||
|
#if (defined(TX_TIMER_TICKS_PER_SECOND) && (!defined(FX_STANDALONE_ENABLE)))
|
||||||
|
#define FX_UPDATE_RATE_IN_TICKS (TX_TIMER_TICKS_PER_SECOND * FX_UPDATE_RATE_IN_SECONDS)
|
||||||
|
#else
|
||||||
|
#define FX_UPDATE_RATE_IN_TICKS 1000
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Define the version ID of FileX. This may be utilized by the application. */
|
||||||
|
|
||||||
|
#ifdef FX_SYSTEM_INIT
|
||||||
|
CHAR _fx_version_id[] =
|
||||||
|
"Copyright (c) 2026 Eclipse ThreadX contributors. * FileX Win64/Visual 6.5.1.202602 *";
|
||||||
|
#else
|
||||||
|
extern CHAR _fx_version_id[];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* FX_PORT_H */
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[ValidateSet('win64', 'win32')]
|
||||||
|
[string]$Arch = 'win64',
|
||||||
|
|
||||||
|
[AllowNull()]
|
||||||
|
[object]$Configuration = 'all',
|
||||||
|
|
||||||
|
[int]$Parallel = [Math]::Max(1, [Environment]::ProcessorCount),
|
||||||
|
|
||||||
|
[int]$BuildTimeoutSeconds = 120,
|
||||||
|
|
||||||
|
[string]$BuildDir,
|
||||||
|
|
||||||
|
[string]$ThreadXDir,
|
||||||
|
|
||||||
|
[switch]$Clean
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
. (Join-Path $PSScriptRoot 'fx_windows_common.ps1')
|
||||||
|
|
||||||
|
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||||
|
$settings = Get-PortSettings -SelectedArch $Arch
|
||||||
|
|
||||||
|
if (-not $BuildDir) {
|
||||||
|
$BuildDir = Join-Path $repoRoot "build\tests\$Arch"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $ThreadXDir) {
|
||||||
|
$ThreadXDir = Join-Path (Split-Path -Parent $repoRoot) 'threadx-fd-codex'
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedConfigurations = Resolve-RegressionConfigurations -RequestedConfigurations $Configuration
|
||||||
|
Write-Host "Selected configurations: $($selectedConfigurations -join ', ')"
|
||||||
|
|
||||||
|
Enter-VisualStudioDevShell -VsArch $settings.VsArch
|
||||||
|
|
||||||
|
foreach ($currentConfiguration in $selectedConfigurations) {
|
||||||
|
$currentBuildDirName = Get-RegressionBuildDirectoryName -ConfigurationName $currentConfiguration
|
||||||
|
$currentBuildDir = Join-Path $BuildDir $currentBuildDirName
|
||||||
|
|
||||||
|
if ($Clean) {
|
||||||
|
Remove-BuildDirectory -Path $currentBuildDir -RepoRoot $repoRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove-NinjaLock -Path $currentBuildDir
|
||||||
|
|
||||||
|
Write-Host "Configuring $Arch / $currentConfiguration"
|
||||||
|
Invoke-NativeCommand -FilePath 'cmake' -Arguments @(
|
||||||
|
'-S', (Join-Path $repoRoot 'test\cmake'),
|
||||||
|
'-B', $currentBuildDir,
|
||||||
|
'-G', 'Ninja',
|
||||||
|
'-DCMAKE_C_COMPILER_FORCED=TRUE',
|
||||||
|
'-DCMAKE_C_COMPILER_WORKS=TRUE',
|
||||||
|
'-DCMAKE_C_ABI_COMPILED=TRUE',
|
||||||
|
# FileX's root CMakeLists.txt declares LANGUAGES C ASM; suppress the
|
||||||
|
# ASM compiler probe because the win64 port has no assembly sources.
|
||||||
|
'-DCMAKE_ASM_COMPILER_FORCED=TRUE',
|
||||||
|
'-DCMAKE_ASM_COMPILER_WORKS=TRUE',
|
||||||
|
"-DCMAKE_BUILD_TYPE=$currentConfiguration",
|
||||||
|
"-DTHREADX_ARCH=$($settings.FileXArch)",
|
||||||
|
"-DTHREADX_TOOLCHAIN=$($settings.FileXToolchain)",
|
||||||
|
"-DTHREADX_SOURCE_DIR=$ThreadXDir"
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "Building $Arch / $currentConfiguration"
|
||||||
|
Invoke-CMakeBuild -BuildDir $currentBuildDir -Parallel $Parallel -TimeoutSeconds $BuildTimeoutSeconds
|
||||||
|
}
|
||||||
@@ -0,0 +1,939 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function Invoke-NativeCommand {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$FilePath,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[string[]]$Arguments = @()
|
||||||
|
)
|
||||||
|
|
||||||
|
& $FilePath @Arguments
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Command failed with exit code ${LASTEXITCODE}: $FilePath $($Arguments -join ' ')"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-CommandPathIfAvailable {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$CommandName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = Get-Command $CommandName -ErrorAction SilentlyContinue
|
||||||
|
if ($null -eq $command) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
return $command.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-PortSettings {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$SelectedArch
|
||||||
|
)
|
||||||
|
|
||||||
|
switch ($SelectedArch) {
|
||||||
|
'win32' {
|
||||||
|
return @{
|
||||||
|
FileXArch = 'win32'
|
||||||
|
FileXToolchain = 'vs_2019'
|
||||||
|
VsArch = 'x86'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'win64' {
|
||||||
|
return @{
|
||||||
|
FileXArch = 'win64'
|
||||||
|
FileXToolchain = 'vs_2022'
|
||||||
|
VsArch = 'amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
throw "Unsupported architecture: $SelectedArch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-RegressionConfigurations {
|
||||||
|
return @(
|
||||||
|
'default_build_coverage',
|
||||||
|
'no_cache_build',
|
||||||
|
'no_cache_standalone_build',
|
||||||
|
'fault_tolerant_build_coverage',
|
||||||
|
'no_check_build',
|
||||||
|
'no_cache_fault_tolerant_build',
|
||||||
|
'standalone_build_coverage',
|
||||||
|
'standalone_fault_tolerant_build_coverage',
|
||||||
|
'standalone_no_cache_fault_tolerant_build'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Resolve-RegressionConfigurations {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[AllowNull()]
|
||||||
|
[AllowEmptyCollection()]
|
||||||
|
[object]$RequestedConfigurations = 'all'
|
||||||
|
)
|
||||||
|
|
||||||
|
$allConfigurations = Get-RegressionConfigurations
|
||||||
|
$resolvedConfigurations = @()
|
||||||
|
|
||||||
|
if ($null -eq $RequestedConfigurations) {
|
||||||
|
$resolvedConfigurations = @('all')
|
||||||
|
}
|
||||||
|
elseif ($RequestedConfigurations -is [System.Array]) {
|
||||||
|
foreach ($requestedConfiguration in $RequestedConfigurations) {
|
||||||
|
if ($null -ne $requestedConfiguration) {
|
||||||
|
$resolvedConfigurations += [string]$requestedConfiguration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$resolvedConfigurations = @([string]$RequestedConfigurations)
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalizedConfigurations = @()
|
||||||
|
foreach ($requestedConfiguration in $resolvedConfigurations) {
|
||||||
|
foreach ($configurationPart in ($requestedConfiguration -split ',')) {
|
||||||
|
$trimmedConfiguration = $configurationPart.Trim()
|
||||||
|
if ($trimmedConfiguration.Length -gt 0) {
|
||||||
|
$normalizedConfigurations += $trimmedConfiguration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($normalizedConfigurations.Count -eq 0) -or ($normalizedConfigurations -contains 'all')) {
|
||||||
|
return $allConfigurations
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($normalizedConfiguration in $normalizedConfigurations) {
|
||||||
|
if ($allConfigurations -notcontains $normalizedConfiguration) {
|
||||||
|
throw "Unsupported configuration: $normalizedConfiguration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $normalizedConfigurations
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-RegressionBuildDirectoryName {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$ConfigurationName
|
||||||
|
)
|
||||||
|
|
||||||
|
switch ($ConfigurationName) {
|
||||||
|
'default_build_coverage' { return 'dbc' }
|
||||||
|
'no_cache_build' { return 'nc' }
|
||||||
|
'no_cache_standalone_build' { return 'ncs' }
|
||||||
|
'fault_tolerant_build_coverage' { return 'ft' }
|
||||||
|
'no_check_build' { return 'nck' }
|
||||||
|
'no_cache_fault_tolerant_build' { return 'ncft' }
|
||||||
|
'standalone_build_coverage' { return 'sa' }
|
||||||
|
'standalone_fault_tolerant_build_coverage' { return 'saft' }
|
||||||
|
'standalone_no_cache_fault_tolerant_build' { return 'sancft' }
|
||||||
|
default {
|
||||||
|
throw "Unsupported configuration: $ConfigurationName"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Enter-VisualStudioDevShell {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$VsArch
|
||||||
|
)
|
||||||
|
|
||||||
|
$targetArch = switch ($VsArch) {
|
||||||
|
'amd64' { 'x64' }
|
||||||
|
'x86' { 'x86' }
|
||||||
|
default { $VsArch }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Get-Command cl -ErrorAction SilentlyContinue) -and ($env:VSCMD_ARG_TGT_ARCH -eq $targetArch)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$vsWherePath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
|
||||||
|
if (-not (Test-Path -LiteralPath $vsWherePath)) {
|
||||||
|
throw "Unable to locate vswhere.exe at $vsWherePath"
|
||||||
|
}
|
||||||
|
|
||||||
|
$installationPath = & $vsWherePath -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
|
||||||
|
if (-not $installationPath) {
|
||||||
|
throw 'Unable to locate a Visual Studio 2022 installation with MSVC build tools.'
|
||||||
|
}
|
||||||
|
|
||||||
|
$launchScript = Join-Path $installationPath 'Common7\Tools\Launch-VsDevShell.ps1'
|
||||||
|
if (-not (Test-Path -LiteralPath $launchScript)) {
|
||||||
|
throw "Unable to locate Launch-VsDevShell.ps1 at $launchScript"
|
||||||
|
}
|
||||||
|
|
||||||
|
$env:VSCMD_SKIP_SENDTELEMETRY = '1'
|
||||||
|
& $launchScript -VsInstallationPath $installationPath -Arch $VsArch -HostArch amd64 -SkipAutomaticLocation | Out-Null
|
||||||
|
|
||||||
|
if (-not (Get-Command cl -ErrorAction SilentlyContinue)) {
|
||||||
|
throw 'MSVC compiler environment was not activated successfully.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-BuildDirectory {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$RepoRoot
|
||||||
|
)
|
||||||
|
|
||||||
|
$fullRepoRoot = [System.IO.Path]::GetFullPath($RepoRoot)
|
||||||
|
$fullPath = [System.IO.Path]::GetFullPath($Path)
|
||||||
|
|
||||||
|
if (-not $fullPath.StartsWith($fullRepoRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||||
|
throw "Refusing to remove a directory outside the repository: $fullPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $fullPath) {
|
||||||
|
try {
|
||||||
|
Remove-Item -LiteralPath $fullPath -Recurse -Force -ErrorAction Stop
|
||||||
|
return
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Failed to remove build directory '$fullPath': $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Get-ChildItem -LiteralPath $fullPath -Force -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
try {
|
||||||
|
if (($_.Attributes -band [System.IO.FileAttributes]::ReadOnly) -ne 0) {
|
||||||
|
$_.Attributes = ($_.Attributes -band (-bnot [System.IO.FileAttributes]::ReadOnly))
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Remove-Item -LiteralPath $fullPath -Recurse -Force -ErrorAction Stop
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Proceeding with partially cleaned build directory '$fullPath': $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-CtestTestingDirectory {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $Path)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop
|
||||||
|
return
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Failed to remove CTest directory '$Path': $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Get-ChildItem -LiteralPath $Path -Force -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
try {
|
||||||
|
if (($_.Attributes -band [System.IO.FileAttributes]::ReadOnly) -ne 0) {
|
||||||
|
$_.Attributes = ($_.Attributes -band (-bnot [System.IO.FileAttributes]::ReadOnly))
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Proceeding with partially cleaned CTest directory '$Path': $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-NinjaLock {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$ninjaLockPath = Join-Path $Path '.ninja_lock'
|
||||||
|
if (Test-Path -LiteralPath $ninjaLockPath) {
|
||||||
|
Remove-Item -LiteralPath $ninjaLockPath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-WindowsDebuggerPath {
|
||||||
|
$debuggerPath = Get-CommandPathIfAvailable -CommandName 'cdb.exe'
|
||||||
|
if ($debuggerPath) {
|
||||||
|
return $debuggerPath
|
||||||
|
}
|
||||||
|
|
||||||
|
$candidatePaths = @(
|
||||||
|
(Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Debuggers\x64\cdb.exe'),
|
||||||
|
(Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Debuggers\x86\cdb.exe')
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($candidatePath in $candidatePaths) {
|
||||||
|
if (Test-Path -LiteralPath $candidatePath) {
|
||||||
|
return $candidatePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SanitizedFileName {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Name
|
||||||
|
)
|
||||||
|
|
||||||
|
$safeName = [regex]::Replace($Name, '[<>:"/\\|?*]', '_')
|
||||||
|
$safeName = $safeName -replace '\s+', '_'
|
||||||
|
return $safeName
|
||||||
|
}
|
||||||
|
|
||||||
|
function Initialize-MinidumpSupport {
|
||||||
|
if ($null -ne ('FileX.WindowsMiniDump' -as [type])) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Add-Type -TypeDefinition @'
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace FileX
|
||||||
|
{
|
||||||
|
public static class WindowsMiniDump
|
||||||
|
{
|
||||||
|
[DllImport("Dbghelp.dll", SetLastError = true)]
|
||||||
|
private static extern bool MiniDumpWriteDump(
|
||||||
|
IntPtr hProcess,
|
||||||
|
uint processId,
|
||||||
|
IntPtr hFile,
|
||||||
|
uint dumpType,
|
||||||
|
IntPtr exceptionParam,
|
||||||
|
IntPtr userStreamParam,
|
||||||
|
IntPtr callbackParam);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern IntPtr OpenProcess(uint desiredAccess, bool inheritHandle, int processId);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern bool CloseHandle(IntPtr handle);
|
||||||
|
|
||||||
|
private const uint ProcessQueryInformation = 0x0400U;
|
||||||
|
private const uint ProcessVmRead = 0x0010U;
|
||||||
|
private const uint ProcessDupHandle = 0x0040U;
|
||||||
|
|
||||||
|
public static bool WriteDump(int processId, string dumpPath, uint dumpType, out int errorCode)
|
||||||
|
{
|
||||||
|
IntPtr processHandle = OpenProcess(ProcessQueryInformation | ProcessVmRead | ProcessDupHandle, false, processId);
|
||||||
|
if (processHandle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
errorCode = Marshal.GetLastWin32Error();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (FileStream dumpStream = new FileStream(dumpPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
|
||||||
|
{
|
||||||
|
bool success = MiniDumpWriteDump(
|
||||||
|
processHandle,
|
||||||
|
unchecked((uint)processId),
|
||||||
|
dumpStream.SafeFileHandle.DangerousGetHandle(),
|
||||||
|
dumpType,
|
||||||
|
IntPtr.Zero,
|
||||||
|
IntPtr.Zero,
|
||||||
|
IntPtr.Zero);
|
||||||
|
|
||||||
|
errorCode = success ? 0 : Marshal.GetLastWin32Error();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CloseHandle(processHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'@
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wait-FileReadable {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[int]$TimeoutSeconds = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||||
|
while ((Get-Date) -lt $deadline) {
|
||||||
|
if (-not (Test-Path -LiteralPath $Path)) {
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$fileStream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
||||||
|
$fileStream.Dispose()
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-CtestTestMetadata {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$BuildDir
|
||||||
|
)
|
||||||
|
|
||||||
|
$ctestOutput = & ctest --test-dir $BuildDir --show-only=json-v1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Unable to enumerate ctest metadata in $BuildDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
return (($ctestOutput -join [Environment]::NewLine) | ConvertFrom-Json).tests
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-CtestFailedTestNames {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$TestingTemporaryDir
|
||||||
|
)
|
||||||
|
|
||||||
|
$lastFailedPath = Join-Path $TestingTemporaryDir 'LastTestsFailed.log'
|
||||||
|
if (-not (Test-Path -LiteralPath $lastFailedPath)) {
|
||||||
|
return @()
|
||||||
|
}
|
||||||
|
|
||||||
|
$failedTestNames = @()
|
||||||
|
foreach ($logLine in (Get-Content -LiteralPath $lastFailedPath)) {
|
||||||
|
if ([string]::IsNullOrWhiteSpace($logLine)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($logLine -match '^\s*\d+:(?<name>.+)\s*$') {
|
||||||
|
$failedTestNames += $Matches['name'].Trim()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$failedTestNames += $logLine.Trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $failedTestNames
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-ProcessDumpCapture {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$ProcessId,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$DumpPath,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[int]$TimeoutSeconds = 15
|
||||||
|
)
|
||||||
|
|
||||||
|
$outputDirectory = Split-Path -Parent $DumpPath
|
||||||
|
if (-not (Test-Path -LiteralPath $outputDirectory)) {
|
||||||
|
New-Item -ItemType Directory -Path $outputDirectory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove-Item -LiteralPath $DumpPath -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Initialize-MinidumpSupport
|
||||||
|
$dumpType = [uint32]0x00001006
|
||||||
|
$errorCode = 0
|
||||||
|
$dumpCaptured = [FileX.WindowsMiniDump]::WriteDump($ProcessId, $DumpPath, $dumpType, [ref]$errorCode)
|
||||||
|
|
||||||
|
if (-not $dumpCaptured) {
|
||||||
|
Write-Warning "MiniDumpWriteDump failed for PID ${ProcessId} with Win32 error $errorCode"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Test-Path -LiteralPath $DumpPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-DumpStackAnalysis {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$DumpPath,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$OutputBasePath,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[string]$SymbolPath,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[int]$TimeoutSeconds = 15
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $DumpPath)) {
|
||||||
|
Write-Warning "Skipping dump analysis because the dump file was not created: $DumpPath"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Wait-FileReadable -Path $DumpPath)) {
|
||||||
|
Write-Warning "Skipping dump analysis because the dump file is not readable yet: $DumpPath"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
$debuggerPath = Get-WindowsDebuggerPath
|
||||||
|
if (-not $debuggerPath) {
|
||||||
|
Write-Warning 'Skipping dump analysis because cdb.exe is not available.'
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
$outputDirectory = Split-Path -Parent $OutputBasePath
|
||||||
|
if (-not (Test-Path -LiteralPath $outputDirectory)) {
|
||||||
|
New-Item -ItemType Directory -Path $outputDirectory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$stdoutPath = "${OutputBasePath}.stdout.txt"
|
||||||
|
$stderrPath = "${OutputBasePath}.stderr.txt"
|
||||||
|
$commandFilePath = "${OutputBasePath}.commands.txt"
|
||||||
|
Set-Content -LiteralPath $commandFilePath -Value @(
|
||||||
|
'!runaway 7'
|
||||||
|
'~* kb 200'
|
||||||
|
'q'
|
||||||
|
) -Encoding Ascii
|
||||||
|
$cdbArguments = @(
|
||||||
|
'-lines',
|
||||||
|
'-z', $DumpPath
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($SymbolPath) {
|
||||||
|
$cdbArguments += @('-y', $SymbolPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
$cdbArguments += @('-cf', $commandFilePath)
|
||||||
|
$cdbProcess = Start-Process -FilePath $debuggerPath -ArgumentList $cdbArguments -PassThru -NoNewWindow `
|
||||||
|
-RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath
|
||||||
|
|
||||||
|
try {
|
||||||
|
$cdbProcess | Wait-Process -Timeout $TimeoutSeconds -ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
if (-not $cdbProcess.HasExited) {
|
||||||
|
$null = Start-Process -FilePath 'taskkill.exe' -ArgumentList @('/PID', $cdbProcess.Id.ToString(), '/T', '/F') `
|
||||||
|
-WindowStyle Hidden -Wait -PassThru
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-ProcessWithTimeout {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$FilePath,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[string[]]$Arguments = @(),
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[int]$TimeoutSeconds = 0,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[string]$WorkingDirectory,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[string]$RedirectStandardOutputPath,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[string]$RedirectStandardErrorPath,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[scriptblock]$OnTimeout,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[scriptblock]$PostTimeout
|
||||||
|
)
|
||||||
|
|
||||||
|
$argumentList = @()
|
||||||
|
foreach ($argument in $Arguments) {
|
||||||
|
if ($argument -match '\s|"') {
|
||||||
|
$argumentList += '"' + ($argument -replace '"', '\"') + '"'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$argumentList += $argument
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$startProcessParameters = @{
|
||||||
|
FilePath = $FilePath
|
||||||
|
NoNewWindow = $true
|
||||||
|
PassThru = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($argumentList.Count -gt 0) {
|
||||||
|
$startProcessParameters['ArgumentList'] = $argumentList
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($WorkingDirectory) {
|
||||||
|
$startProcessParameters['WorkingDirectory'] = $WorkingDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($RedirectStandardOutputPath) {
|
||||||
|
$startProcessParameters['RedirectStandardOutput'] = $RedirectStandardOutputPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($RedirectStandardErrorPath) {
|
||||||
|
$startProcessParameters['RedirectStandardError'] = $RedirectStandardErrorPath
|
||||||
|
}
|
||||||
|
|
||||||
|
$process = Start-Process @startProcessParameters
|
||||||
|
if ($TimeoutSeconds -le 0) {
|
||||||
|
$process | Wait-Process
|
||||||
|
$completed = $true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
$process | Wait-Process -Timeout $TimeoutSeconds -ErrorAction Stop
|
||||||
|
$completed = $true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$completed = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $completed) {
|
||||||
|
if ($null -ne $OnTimeout) {
|
||||||
|
& $OnTimeout $process
|
||||||
|
}
|
||||||
|
|
||||||
|
$null = Start-Process -FilePath 'taskkill.exe' -ArgumentList @('/PID', $process.Id.ToString(), '/T', '/F') -WindowStyle Hidden -Wait -PassThru
|
||||||
|
|
||||||
|
if ($null -ne $PostTimeout) {
|
||||||
|
& $PostTimeout $process
|
||||||
|
}
|
||||||
|
|
||||||
|
return @{
|
||||||
|
Completed = $false
|
||||||
|
ExitCode = $null
|
||||||
|
ProcessId = $process.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$process.Refresh()
|
||||||
|
return @{
|
||||||
|
Completed = $true
|
||||||
|
ExitCode = $process.ExitCode
|
||||||
|
ProcessId = $process.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-CtestFailureDiagnostics {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$BuildDir,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$TestingTemporaryDir,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$TimeoutSeconds
|
||||||
|
)
|
||||||
|
|
||||||
|
$failedTestNames = Get-CtestFailedTestNames -TestingTemporaryDir $TestingTemporaryDir
|
||||||
|
if ($failedTestNames.Count -eq 0) {
|
||||||
|
Write-Warning "No failed tests were recorded in $TestingTemporaryDir"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$testMetadataList = Get-CtestTestMetadata -BuildDir $BuildDir
|
||||||
|
$testMetadataMap = @{}
|
||||||
|
foreach ($testMetadata in $testMetadataList) {
|
||||||
|
$testMetadataMap[$testMetadata.name] = $testMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
$diagnosticsRoot = Join-Path $TestingTemporaryDir 'FailureDiagnostics'
|
||||||
|
if (-not (Test-Path -LiteralPath $diagnosticsRoot)) {
|
||||||
|
New-Item -ItemType Directory -Path $diagnosticsRoot | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($failedTestName in $failedTestNames) {
|
||||||
|
if (-not $testMetadataMap.ContainsKey($failedTestName)) {
|
||||||
|
Write-Warning "Unable to locate ctest metadata for failed test: $failedTestName"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$testMetadata = $testMetadataMap[$failedTestName]
|
||||||
|
if (($null -eq $testMetadata.command) -or ($testMetadata.command.Count -eq 0)) {
|
||||||
|
Write-Warning "No executable command was recorded for failed test: $failedTestName"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$testArguments = @()
|
||||||
|
if ($testMetadata.command.Count -gt 1) {
|
||||||
|
$testArguments = @($testMetadata.command[1..($testMetadata.command.Count - 1)])
|
||||||
|
}
|
||||||
|
|
||||||
|
$safeTestName = Get-SanitizedFileName -Name $failedTestName
|
||||||
|
$stdoutPath = Join-Path $diagnosticsRoot "${safeTestName}.stdout.txt"
|
||||||
|
$stderrPath = Join-Path $diagnosticsRoot "${safeTestName}.stderr.txt"
|
||||||
|
$debugOutputBasePath = Join-Path $diagnosticsRoot "${safeTestName}.cdb"
|
||||||
|
$workingDirectory = $null
|
||||||
|
$symbolDirectory = Split-Path -Parent $testMetadata.command[0]
|
||||||
|
|
||||||
|
if ($null -ne $testMetadata.properties) {
|
||||||
|
foreach ($testProperty in $testMetadata.properties) {
|
||||||
|
if ($testProperty.name -eq 'WORKING_DIRECTORY') {
|
||||||
|
$workingDirectory = $testProperty.value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Warning "Collecting failure diagnostics for $failedTestName"
|
||||||
|
$dumpPath = '{0}.{1}.dmp' -f $debugOutputBasePath, ([DateTime]::UtcNow.ToString('yyyyMMddHHmmssfff'))
|
||||||
|
$testResult = Invoke-ProcessWithTimeout -FilePath $testMetadata.command[0] -Arguments $testArguments `
|
||||||
|
-TimeoutSeconds $TimeoutSeconds -WorkingDirectory $workingDirectory -RedirectStandardOutputPath $stdoutPath `
|
||||||
|
-RedirectStandardErrorPath $stderrPath -OnTimeout {
|
||||||
|
param($timedOutProcess)
|
||||||
|
Invoke-ProcessDumpCapture -ProcessId $timedOutProcess.Id -DumpPath $dumpPath | Out-Null
|
||||||
|
} -PostTimeout {
|
||||||
|
param($timedOutProcess)
|
||||||
|
if (Test-Path -LiteralPath $dumpPath) {
|
||||||
|
Invoke-DumpStackAnalysis -DumpPath $dumpPath -OutputBasePath $debugOutputBasePath -SymbolPath $symbolDirectory | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $testResult.Completed) {
|
||||||
|
Write-Warning "Timeout diagnostics were captured for $failedTestName under $diagnosticsRoot"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Warning "Replay finished for $failedTestName with exit code $($testResult.ExitCode). Output was saved under $diagnosticsRoot"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-IsNinjaBuildDirectory {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$BuildDir
|
||||||
|
)
|
||||||
|
|
||||||
|
return (Test-Path -LiteralPath (Join-Path $BuildDir 'build.ninja'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-NinjaBuildStatements {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$BuildDir
|
||||||
|
)
|
||||||
|
|
||||||
|
$buildNinjaPath = Join-Path $BuildDir 'build.ninja'
|
||||||
|
if (-not (Test-Path -LiteralPath $buildNinjaPath)) {
|
||||||
|
throw "Unable to locate build.ninja in $BuildDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
return Get-Content -LiteralPath $buildNinjaPath
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-NinjaRspFile {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$BuildDir,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$RspRelativePath
|
||||||
|
)
|
||||||
|
|
||||||
|
$buildStatements = Get-NinjaBuildStatements -BuildDir $BuildDir
|
||||||
|
$rspLine = ' RSP_FILE = ' + $RspRelativePath
|
||||||
|
$rspIndex = -1
|
||||||
|
|
||||||
|
for ($index = 0; $index -lt $buildStatements.Count; $index++) {
|
||||||
|
if ($buildStatements[$index] -eq $rspLine) {
|
||||||
|
$rspIndex = $index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rspIndex -lt 0) {
|
||||||
|
throw "Unable to locate RSP_FILE entry for $RspRelativePath in build.ninja."
|
||||||
|
}
|
||||||
|
|
||||||
|
$buildIndex = -1
|
||||||
|
for ($index = $rspIndex; $index -ge 0; $index--) {
|
||||||
|
if ($buildStatements[$index].StartsWith('build ')) {
|
||||||
|
$buildIndex = $index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($buildIndex -lt 0) {
|
||||||
|
throw "Unable to locate the build statement that owns $RspRelativePath."
|
||||||
|
}
|
||||||
|
|
||||||
|
$buildLine = $buildStatements[$buildIndex]
|
||||||
|
if ($buildLine -notmatch '^build\s+\S+:\s+\S+\s+(.+)$') {
|
||||||
|
throw "Unable to parse build statement for $RspRelativePath."
|
||||||
|
}
|
||||||
|
|
||||||
|
$rspContents = ($Matches[1] -split '\s+') -join [Environment]::NewLine
|
||||||
|
$rspPath = Join-Path $BuildDir $RspRelativePath
|
||||||
|
$rspParent = Split-Path -Parent $rspPath
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $rspParent)) {
|
||||||
|
New-Item -ItemType Directory -Path $rspParent | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Content -LiteralPath $rspPath -Value $rspContents
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-NinjaRspFiles {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$BuildDir,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$CommandLine
|
||||||
|
)
|
||||||
|
|
||||||
|
$rspMatches = [regex]::Matches($CommandLine, '@(?<path>[^\s"]+\.rsp)')
|
||||||
|
foreach ($rspMatch in $rspMatches) {
|
||||||
|
$rspRelativePath = $rspMatch.Groups['path'].Value
|
||||||
|
$rspPath = Join-Path $BuildDir $rspRelativePath
|
||||||
|
if (-not (Test-Path -LiteralPath $rspPath)) {
|
||||||
|
New-NinjaRspFile -BuildDir $BuildDir -RspRelativePath $rspRelativePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-PendingNinjaCommands {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$BuildDir
|
||||||
|
)
|
||||||
|
|
||||||
|
$commandLines = & ninja -C $BuildDir -t commands
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Unable to enumerate pending Ninja commands in $BuildDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
return $commandLines | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-NinjaFallbackBuild {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$BuildDir
|
||||||
|
)
|
||||||
|
|
||||||
|
$pendingCommands = Get-PendingNinjaCommands -BuildDir $BuildDir
|
||||||
|
if ($pendingCommands.Count -eq 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location $BuildDir
|
||||||
|
try {
|
||||||
|
foreach ($pendingCommand in $pendingCommands) {
|
||||||
|
Ensure-NinjaRspFiles -BuildDir $BuildDir -CommandLine $pendingCommand
|
||||||
|
|
||||||
|
$commandToRun = $pendingCommand -replace '\s/showIncludes(?=\s|$)', ''
|
||||||
|
|
||||||
|
if ($commandToRun -match '^[^ ]*cmd(?:\.exe)?\s+/C\s+"(?<inner>.*)"\s*$') {
|
||||||
|
& cmd.exe /C $Matches['inner']
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
& cmd.exe /C $commandToRun
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Fallback Ninja command failed with exit code ${LASTEXITCODE}: $pendingCommand"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-CMakeBuild {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$BuildDir,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$Parallel,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[int]$TimeoutSeconds = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
Remove-NinjaLock -Path $BuildDir
|
||||||
|
$isNinjaBuild = Test-IsNinjaBuildDirectory -BuildDir $BuildDir
|
||||||
|
|
||||||
|
if ($TimeoutSeconds -le 0) {
|
||||||
|
if ($isNinjaBuild) {
|
||||||
|
Invoke-NativeCommand -FilePath 'ninja' -Arguments @(
|
||||||
|
'-C', $BuildDir,
|
||||||
|
'-j', $Parallel.ToString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Invoke-NativeCommand -FilePath 'cmake' -Arguments @(
|
||||||
|
'--build', $BuildDir,
|
||||||
|
'--parallel', $Parallel.ToString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isNinjaBuild) {
|
||||||
|
$buildToolName = 'Ninja'
|
||||||
|
$buildResult = Invoke-ProcessWithTimeout -FilePath 'ninja' -Arguments @(
|
||||||
|
'-C', $BuildDir,
|
||||||
|
'-j', $Parallel.ToString()
|
||||||
|
) -TimeoutSeconds $TimeoutSeconds
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$buildToolName = 'CMake'
|
||||||
|
$buildResult = Invoke-ProcessWithTimeout -FilePath 'cmake' -Arguments @(
|
||||||
|
'--build', $BuildDir,
|
||||||
|
'--parallel', $Parallel.ToString()
|
||||||
|
) -TimeoutSeconds $TimeoutSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($buildResult.Completed -and ($buildResult.ExitCode -eq 0)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $isNinjaBuild) {
|
||||||
|
if (-not $buildResult.Completed) {
|
||||||
|
throw "$buildToolName build timed out after $TimeoutSeconds seconds in $BuildDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
throw "$buildToolName build failed with exit code $($buildResult.ExitCode) in $BuildDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($buildResult.Completed) {
|
||||||
|
throw "$buildToolName build failed with exit code $($buildResult.ExitCode) in $BuildDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Warning "$buildToolName build timed out after $TimeoutSeconds seconds in $BuildDir. Replaying pending Ninja commands from PowerShell."
|
||||||
|
|
||||||
|
Remove-NinjaLock -Path $BuildDir
|
||||||
|
Invoke-NinjaFallbackBuild -BuildDir $BuildDir
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[ValidateSet('win64', 'win32')]
|
||||||
|
[string]$Arch = 'win64',
|
||||||
|
|
||||||
|
[AllowNull()]
|
||||||
|
[object]$Configuration = 'all',
|
||||||
|
|
||||||
|
[int]$Parallel = [Math]::Max(1, [Environment]::ProcessorCount),
|
||||||
|
|
||||||
|
[int]$RepeatFailCount = 1,
|
||||||
|
|
||||||
|
[int]$TestTimeoutSeconds = 60,
|
||||||
|
|
||||||
|
[switch]$CollectFailureDiagnostics = $true,
|
||||||
|
|
||||||
|
[string]$TestRegex,
|
||||||
|
|
||||||
|
[switch]$RerunFailedOnly,
|
||||||
|
|
||||||
|
[string]$BuildDir,
|
||||||
|
|
||||||
|
[switch]$Clean
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
. (Join-Path $PSScriptRoot 'fx_windows_common.ps1')
|
||||||
|
|
||||||
|
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||||
|
$settings = Get-PortSettings -SelectedArch $Arch
|
||||||
|
|
||||||
|
if (-not $BuildDir) {
|
||||||
|
$BuildDir = Join-Path $repoRoot "build\tests\$Arch"
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedConfigurations = Resolve-RegressionConfigurations -RequestedConfigurations $Configuration
|
||||||
|
Write-Host "Selected configurations: $($selectedConfigurations -join ', ')"
|
||||||
|
|
||||||
|
Enter-VisualStudioDevShell -VsArch $settings.VsArch
|
||||||
|
|
||||||
|
if (($settings.FileXArch -eq 'win32') -or ($settings.FileXArch -eq 'win64')) {
|
||||||
|
if ($Parallel -ne 1) {
|
||||||
|
Write-Warning "Windows simulator regression tests are timing-sensitive under concurrent ctest execution. Forcing -Parallel 1."
|
||||||
|
$Parallel = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$failedConfigurations = @()
|
||||||
|
|
||||||
|
foreach ($currentConfiguration in $selectedConfigurations) {
|
||||||
|
$currentBuildDirName = Get-RegressionBuildDirectoryName -ConfigurationName $currentConfiguration
|
||||||
|
$currentBuildDir = Join-Path $BuildDir $currentBuildDirName
|
||||||
|
$currentTestingTemporaryDir = Join-Path $currentBuildDir 'Testing\Temporary'
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($Clean) {
|
||||||
|
$currentTestingDir = Join-Path $currentBuildDir 'Testing'
|
||||||
|
Remove-CtestTestingDirectory -Path $currentTestingDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $currentBuildDir)) {
|
||||||
|
throw "Build directory does not exist for $Arch / ${currentConfiguration}: $currentBuildDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove-NinjaLock -Path $currentBuildDir
|
||||||
|
if (Test-Path -LiteralPath $currentTestingTemporaryDir) {
|
||||||
|
Remove-Item -LiteralPath (Join-Path $currentTestingTemporaryDir 'LastTest.log') -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item -LiteralPath (Join-Path $currentTestingTemporaryDir 'LastTestsFailed.log') -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Testing $Arch / $currentConfiguration"
|
||||||
|
$ctestArguments = @(
|
||||||
|
'--test-dir', $currentBuildDir,
|
||||||
|
'--output-on-failure',
|
||||||
|
'--timeout', $TestTimeoutSeconds.ToString(),
|
||||||
|
'-j', $Parallel.ToString()
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($RepeatFailCount -gt 1) {
|
||||||
|
$ctestArguments += @('--repeat', "until-pass:$RepeatFailCount")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($TestRegex) {
|
||||||
|
$ctestArguments += @('-R', $TestRegex)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($RerunFailedOnly) {
|
||||||
|
$ctestArguments += '--rerun-failed'
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-NativeCommand -FilePath 'ctest' -Arguments $ctestArguments
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
if ($CollectFailureDiagnostics -and (Test-Path -LiteralPath $currentBuildDir)) {
|
||||||
|
try {
|
||||||
|
Invoke-CtestFailureDiagnostics -BuildDir $currentBuildDir -TestingTemporaryDir $currentTestingTemporaryDir `
|
||||||
|
-TimeoutSeconds $TestTimeoutSeconds
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Failure diagnostics collection failed for ${currentConfiguration}: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$failedConfigurations += @{
|
||||||
|
Configuration = $currentConfiguration
|
||||||
|
Message = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Warning "Configuration failed: $currentConfiguration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($failedConfigurations.Count -gt 0) {
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host 'Configuration failure summary:'
|
||||||
|
foreach ($failedConfiguration in $failedConfigurations) {
|
||||||
|
Write-Host "- $($failedConfiguration.Configuration): $($failedConfiguration.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
throw "One or more configurations failed: $($failedConfigurations.Configuration -join ', ')"
|
||||||
|
}
|
||||||
+70
-37
@@ -2,7 +2,17 @@ cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
|
|||||||
cmake_policy(SET CMP0054 NEW)
|
cmake_policy(SET CMP0054 NEW)
|
||||||
cmake_policy(SET CMP0057 NEW)
|
cmake_policy(SET CMP0057 NEW)
|
||||||
|
|
||||||
project(threadx_test LANGUAGES C)
|
# Suppress try_compile link step when MSVC cannot produce a runnable executable
|
||||||
|
# (e.g. when the VS environment is not fully initialised for a given target arch).
|
||||||
|
if((DEFINED THREADX_ARCH) AND ((THREADX_ARCH STREQUAL "win32") OR (THREADX_ARCH STREQUAL "win64")))
|
||||||
|
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
project(filex_test LANGUAGES C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
# Set build configurations
|
# Set build configurations
|
||||||
set(BUILD_CONFIGURATIONS
|
set(BUILD_CONFIGURATIONS
|
||||||
@@ -31,25 +41,32 @@ set(FX_FAULT_TOLERANT_DEFINITIONS
|
|||||||
set(default_build_coverage "")
|
set(default_build_coverage "")
|
||||||
set(no_cache_build -DFX_DISABLE_CACHE -DFX_DISABLE_FAT_ENTRY_REFRESH)
|
set(no_cache_build -DFX_DISABLE_CACHE -DFX_DISABLE_FAT_ENTRY_REFRESH)
|
||||||
set(fault_tolerant_build_coverage ${FX_FAULT_TOLERANT_DEFINITIONS})
|
set(fault_tolerant_build_coverage ${FX_FAULT_TOLERANT_DEFINITIONS})
|
||||||
set(standalone_build_coverage -DFX_STANDALONE_ENABLE)
|
set(standalone_build_coverage -DFX_STANDALONE_ENABLE)
|
||||||
set(standalone_fault_tolerant_build_coverage ${FX_FAULT_TOLERANT_DEFINITIONS}
|
set(standalone_fault_tolerant_build_coverage ${FX_FAULT_TOLERANT_DEFINITIONS}
|
||||||
-DFX_STANDALONE_ENABLE)
|
-DFX_STANDALONE_ENABLE)
|
||||||
set(no_cache_standalone_build -DFX_DISABLE_CACHE -DFX_STANDALONE_ENABLE)
|
set(no_cache_standalone_build -DFX_DISABLE_CACHE -DFX_STANDALONE_ENABLE)
|
||||||
set(no_check_build ${FX_COMPILE_DEFINITIONS} -DFX_DISABLE_ERROR_CHECKING)
|
set(no_check_build -DFX_DISABLE_ERROR_CHECKING)
|
||||||
set(no_cache_fault_tolerant_build ${no_cache_build} ${FX_FAULT_TOLERANT_DEFINITIONS})
|
set(no_cache_fault_tolerant_build ${no_cache_build} ${FX_FAULT_TOLERANT_DEFINITIONS})
|
||||||
set(standalone_no_cache_fault_tolerant_build ${no_cache_build} ${FX_FAULT_TOLERANT_DEFINITIONS} -DFX_STANDALONE_ENABLE)
|
set(standalone_no_cache_fault_tolerant_build ${no_cache_build} ${FX_FAULT_TOLERANT_DEFINITIONS} -DFX_STANDALONE_ENABLE)
|
||||||
|
|
||||||
add_compile_options(
|
if(MSVC)
|
||||||
-m32
|
add_compile_options(/W3 /Zi)
|
||||||
-std=c99
|
add_link_options(/DEBUG /INCREMENTAL:NO)
|
||||||
-ggdb
|
# Both cl.exe and gcc accept -DFOO; use it to keep the per-configuration
|
||||||
-g3
|
# definitions consistent with the Linux path.
|
||||||
-gdwarf-2
|
add_compile_options(-DFX_REGRESSION_TEST ${${CMAKE_BUILD_TYPE}})
|
||||||
-fdiagnostics-color
|
else()
|
||||||
-Werror
|
add_compile_options(
|
||||||
-DFX_REGRESSION_TEST
|
-m32
|
||||||
${${CMAKE_BUILD_TYPE}})
|
-ggdb
|
||||||
add_link_options(-m32)
|
-g3
|
||||||
|
-gdwarf-2
|
||||||
|
-fdiagnostics-color
|
||||||
|
-Werror
|
||||||
|
-DFX_REGRESSION_TEST
|
||||||
|
${${CMAKE_BUILD_TYPE}})
|
||||||
|
add_link_options(-m32)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE MATCHES ".*standalone.*")
|
if(CMAKE_BUILD_TYPE MATCHES ".*standalone.*")
|
||||||
set(FX_STANDALONE_ENABLE
|
set(FX_STANDALONE_ENABLE
|
||||||
@@ -59,18 +76,30 @@ endif()
|
|||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
|
# ThreadX dependency: on Windows build from source; on Linux use run.sh.
|
||||||
|
if(MSVC)
|
||||||
|
if(NOT FX_STANDALONE_ENABLE)
|
||||||
|
if(NOT DEFINED THREADX_SOURCE_DIR)
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"THREADX_SOURCE_DIR must point to a ThreadX source tree for non-standalone Windows builds. "
|
||||||
|
"Pass -DTHREADX_SOURCE_DIR=<path> to cmake, or use build_fx.ps1 which sets it automatically.")
|
||||||
|
endif()
|
||||||
|
add_subdirectory(${THREADX_SOURCE_DIR} threadx_build)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../.. filex)
|
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../.. filex)
|
||||||
add_subdirectory(regression)
|
add_subdirectory(regression)
|
||||||
add_subdirectory(samples)
|
add_subdirectory(samples)
|
||||||
|
|
||||||
# Coverage
|
# Coverage instrumentation (GCC/Clang only — not supported by MSVC)
|
||||||
if(CMAKE_BUILD_TYPE MATCHES ".*_coverage")
|
if(NOT MSVC AND CMAKE_BUILD_TYPE MATCHES ".*_coverage")
|
||||||
target_compile_options(filex PRIVATE -fprofile-arcs -ftest-coverage)
|
target_compile_options(filex PRIVATE -fprofile-arcs -ftest-coverage)
|
||||||
target_link_options(filex PRIVATE -fprofile-arcs -ftest-coverage)
|
target_link_options(filex PRIVATE -fprofile-arcs -ftest-coverage)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Build ThreadX library once
|
# Build ThreadX shared library for Linux
|
||||||
if(NOT FX_STANDALONE_ENABLE)
|
if(NOT MSVC AND NOT FX_STANDALONE_ENABLE)
|
||||||
execute_process(COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh build_libs)
|
execute_process(COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh build_libs)
|
||||||
add_custom_target(build_libs ALL COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh
|
add_custom_target(build_libs ALL COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh
|
||||||
build_libs)
|
build_libs)
|
||||||
@@ -83,23 +112,27 @@ if(NOT FX_STANDALONE_ENABLE)
|
|||||||
${CMAKE_BINARY_DIR}/../libs/threadx/libthreadx.so)
|
${CMAKE_BINARY_DIR}/../libs/threadx/libthreadx.so)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_compile_options(
|
if(MSVC)
|
||||||
filex
|
target_compile_options(filex PRIVATE /W3)
|
||||||
PRIVATE -Werror
|
else()
|
||||||
-Wall
|
target_compile_options(
|
||||||
-Wextra
|
filex
|
||||||
-pedantic
|
PRIVATE -Werror
|
||||||
-fmessage-length=0
|
-Wall
|
||||||
-fsigned-char
|
-Wextra
|
||||||
-ffunction-sections
|
-pedantic
|
||||||
-fdata-sections
|
-fmessage-length=0
|
||||||
-Wunused
|
-fsigned-char
|
||||||
-Wuninitialized
|
-ffunction-sections
|
||||||
-Wmissing-declarations
|
-fdata-sections
|
||||||
-Wconversion
|
-Wunused
|
||||||
-Wpointer-arith
|
-Wuninitialized
|
||||||
-Wshadow
|
-Wmissing-declarations
|
||||||
-Wlogical-op
|
-Wconversion
|
||||||
-Waggregate-return
|
-Wpointer-arith
|
||||||
-Wfloat-equal)
|
-Wshadow
|
||||||
|
-Wlogical-op
|
||||||
|
-Waggregate-return
|
||||||
|
-Wfloat-equal)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ add_library(test_utility ${SOURCE_DIR}/fx_ram_driver_test.c
|
|||||||
${SOURCE_DIR}/filextestcontrol.c)
|
${SOURCE_DIR}/filextestcontrol.c)
|
||||||
target_link_libraries(test_utility PUBLIC azrtos::filex)
|
target_link_libraries(test_utility PUBLIC azrtos::filex)
|
||||||
target_compile_definitions(test_utility PUBLIC BATCH_TEST CTEST)
|
target_compile_definitions(test_utility PUBLIC BATCH_TEST CTEST)
|
||||||
|
if(MSVC AND FX_STANDALONE_ENABLE)
|
||||||
|
target_include_directories(test_utility PUBLIC ${SOURCE_DIR}/win64_compat)
|
||||||
|
endif()
|
||||||
|
|
||||||
foreach(test_case ${regression_test_cases})
|
foreach(test_case ${regression_test_cases})
|
||||||
get_filename_component(test_name ${test_case} NAME_WE)
|
get_filename_component(test_name ${test_case} NAME_WE)
|
||||||
@@ -154,3 +157,14 @@ foreach(test_case ${regression_test_cases})
|
|||||||
target_link_libraries(${test_name} PRIVATE test_utility)
|
target_link_libraries(${test_name} PRIVATE test_utility)
|
||||||
add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_name})
|
add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_name})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
# These two tests write or corrupt large amounts of data on a 900 MB RAM disk and are
|
||||||
|
# inherently slow (≈95 s and ≈330 s on Windows). Override the default 60-second ctest
|
||||||
|
# timeout so that they are not mis-reported as failures.
|
||||||
|
if(MSVC)
|
||||||
|
set_tests_properties(
|
||||||
|
${CMAKE_BUILD_TYPE}::filex_fault_tolerant_file_corruption_test
|
||||||
|
${CMAKE_BUILD_TYPE}::filex_fault_tolerant_media_full_test
|
||||||
|
PROPERTIES TIMEOUT 600
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ foreach(sample_file ${sample_files})
|
|||||||
get_filename_component(sample_file_name ${sample_file} NAME_WE)
|
get_filename_component(sample_file_name ${sample_file} NAME_WE)
|
||||||
add_executable(${sample_file_name} ${sample_file}
|
add_executable(${sample_file_name} ${sample_file}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../regression_test/fx_ram_driver_test.c)
|
${CMAKE_CURRENT_LIST_DIR}/../../regression_test/fx_ram_driver_test.c)
|
||||||
target_compile_options(${sample_file_name} PRIVATE -DSAMPLE_BUILD)
|
target_compile_definitions(${sample_file_name} PRIVATE SAMPLE_BUILD)
|
||||||
target_link_libraries(${sample_file_name} PRIVATE azrtos::filex)
|
target_link_libraries(${sample_file_name} PRIVATE azrtos::filex)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* */
|
/* */
|
||||||
/* Copyright (c) 1996-2017 by Express Logic Inc. */
|
/* Copyright (c) 1996-2017 by Express Logic Inc. */
|
||||||
/* */
|
/* */
|
||||||
@@ -81,6 +81,36 @@ ULONG64 media_size;
|
|||||||
UCHAR large_file_name_format[];
|
UCHAR large_file_name_format[];
|
||||||
|
|
||||||
/* Define memory for tests to be run in standalone mode (without Azure RTOS: ThreadX) */
|
/* Define memory for tests to be run in standalone mode (without Azure RTOS: ThreadX) */
|
||||||
|
/* On MSVC, BSS arrays cause demand-zero page faults on first access, which serialises
|
||||||
|
with test execution and makes large-disk tests significantly slower than on Linux.
|
||||||
|
Pre-allocating with calloc commits and zero-fills all pages before any test runs,
|
||||||
|
eliminating page-fault overhead at runtime. This also avoids LNK1248 (PE image > 2 GB)
|
||||||
|
for configurations that would otherwise exceed the hard linker limit.
|
||||||
|
On other compilers keep the BSS arrays as before. */
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <stdlib.h>
|
||||||
|
UCHAR *ram_disk_memory_large = NULL;
|
||||||
|
UCHAR *large_data_buffer = NULL;
|
||||||
|
|
||||||
|
#if defined(FX_STANDALONE_ENABLE) && !defined(SAMPLE_BUILD)
|
||||||
|
UCHAR *ram_disk_memory = NULL;
|
||||||
|
UCHAR *ram_disk_memory1 = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void _fx_alloc_test_buffers(void)
|
||||||
|
{
|
||||||
|
ram_disk_memory_large = (UCHAR *)calloc(900000000UL, 1U);
|
||||||
|
large_data_buffer = (UCHAR *)calloc(900000000UL, 1U);
|
||||||
|
#if defined(FX_STANDALONE_ENABLE) && !defined(SAMPLE_BUILD)
|
||||||
|
ram_disk_memory = (UCHAR *)calloc(300000000UL, 1U);
|
||||||
|
ram_disk_memory1 = (UCHAR *)calloc( 30000000UL, 1U);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#pragma section(".CRT$XCU", read)
|
||||||
|
__declspec(allocate(".CRT$XCU")) static void (*_p_fx_alloc)(void) = _fx_alloc_test_buffers;
|
||||||
|
|
||||||
|
#else /* !_MSC_VER */
|
||||||
|
|
||||||
#if defined(FX_STANDALONE_ENABLE) && !defined(SAMPLE_BUILD)
|
#if defined(FX_STANDALONE_ENABLE) && !defined(SAMPLE_BUILD)
|
||||||
UCHAR ram_disk_memory[300000000];
|
UCHAR ram_disk_memory[300000000];
|
||||||
UCHAR ram_disk_memory1[30000000];
|
UCHAR ram_disk_memory1[30000000];
|
||||||
@@ -88,6 +118,7 @@ UCHAR ram_disk_memory1[30000000];
|
|||||||
|
|
||||||
UCHAR ram_disk_memory_large[900000000];
|
UCHAR ram_disk_memory_large[900000000];
|
||||||
UCHAR large_data_buffer[900000000];
|
UCHAR large_data_buffer[900000000];
|
||||||
|
#endif /* _MSC_VER */
|
||||||
|
|
||||||
|
|
||||||
/* Define the callback function. */
|
/* Define the callback function. */
|
||||||
@@ -298,8 +329,16 @@ UINT offset;
|
|||||||
/* Calculate the RAM disk sector offset. Note the RAM disk memory is pointed to by
|
/* Calculate the RAM disk sector offset. Note the RAM disk memory is pointed to by
|
||||||
the fx_media_driver_info pointer, which is supplied by the application in the
|
the fx_media_driver_info pointer, which is supplied by the application in the
|
||||||
call to fx_media_open. */
|
call to fx_media_open. */
|
||||||
source_buffer = ((UCHAR *) media_ptr -> fx_media_driver_info) +
|
{
|
||||||
((media_ptr -> fx_media_driver_logical_sector + media_ptr -> fx_media_hidden_sectors) * media_ptr -> fx_media_bytes_per_sector);
|
/* Compute effective sector and guard against pointer arithmetic overflow on 64-bit
|
||||||
|
platforms when sector is near ULONG64_MAX. Out-of-range accesses are redirected
|
||||||
|
to large_data_buffer so the test can verify return codes without crashing. */
|
||||||
|
ULONG64 _sector = media_ptr -> fx_media_driver_logical_sector + media_ptr -> fx_media_hidden_sectors;
|
||||||
|
if (media_ptr -> fx_media_total_sectors > 0 && _sector >= media_ptr -> fx_media_total_sectors)
|
||||||
|
source_buffer = large_data_buffer; /* harmless scratch — test does not verify data */
|
||||||
|
else
|
||||||
|
source_buffer = ((UCHAR *) media_ptr -> fx_media_driver_info) + (_sector * media_ptr -> fx_media_bytes_per_sector);
|
||||||
|
}
|
||||||
|
|
||||||
/* Copy the RAM sector into the destination. */
|
/* Copy the RAM sector into the destination. */
|
||||||
_fx_utility_memory_copy(source_buffer, media_ptr -> fx_media_driver_buffer,
|
_fx_utility_memory_copy(source_buffer, media_ptr -> fx_media_driver_buffer,
|
||||||
@@ -318,8 +357,17 @@ UINT offset;
|
|||||||
/* Calculate the RAM disk sector offset. Note the RAM disk memory is pointed to by
|
/* Calculate the RAM disk sector offset. Note the RAM disk memory is pointed to by
|
||||||
the fx_media_driver_info pointer, which is supplied by the application in the
|
the fx_media_driver_info pointer, which is supplied by the application in the
|
||||||
call to fx_media_open. */
|
call to fx_media_open. */
|
||||||
data_start = ((UCHAR *) media_ptr -> fx_media_driver_info) +
|
{
|
||||||
((media_ptr -> fx_media_driver_logical_sector + media_ptr -> fx_media_hidden_sectors) * media_ptr -> fx_media_bytes_per_sector);
|
/* Compute effective sector and guard against pointer arithmetic overflow on 64-bit
|
||||||
|
platforms when sector is near ULONG64_MAX. Out-of-range writes are discarded. */
|
||||||
|
ULONG64 _sector = media_ptr -> fx_media_driver_logical_sector + media_ptr -> fx_media_hidden_sectors;
|
||||||
|
if (media_ptr -> fx_media_total_sectors > 0 && _sector >= media_ptr -> fx_media_total_sectors)
|
||||||
|
{
|
||||||
|
media_ptr -> fx_media_driver_status = FX_SUCCESS;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data_start = ((UCHAR *) media_ptr -> fx_media_driver_info) + (_sector * media_ptr -> fx_media_bytes_per_sector);
|
||||||
|
}
|
||||||
|
|
||||||
/* Driver write callback function entry for calling. */
|
/* Driver write callback function entry for calling. */
|
||||||
if (driver_write_callback != FX_NULL)
|
if (driver_write_callback != FX_NULL)
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
/* Copyright (c) 2025 Contributors to the Eclipse Foundation */
|
||||||
|
/* AI-generated with GitHub Copilot */
|
||||||
|
|
||||||
|
/* Minimal pthreads shim for FileX regression tests on Windows/x64. */
|
||||||
|
|
||||||
|
#ifndef WIN64_COMPAT_PTHREAD_H
|
||||||
|
#define WIN64_COMPAT_PTHREAD_H
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
typedef HANDLE pthread_t;
|
||||||
|
typedef void pthread_attr_t;
|
||||||
|
|
||||||
|
#define PTHREAD_CANCEL_ENABLE 0
|
||||||
|
#define PTHREAD_CANCEL_DISABLE 1
|
||||||
|
#define PTHREAD_CANCEL_DEFERRED 0
|
||||||
|
#define PTHREAD_CANCEL_ASYNCHRONOUS 1
|
||||||
|
|
||||||
|
static inline int pthread_create(pthread_t *tid, const pthread_attr_t *attr,
|
||||||
|
void *(*start_routine)(void *), void *arg)
|
||||||
|
{
|
||||||
|
(void)attr;
|
||||||
|
*tid = CreateThread(NULL, 0U, (LPTHREAD_START_ROUTINE)(void *)start_routine, arg, 0U, NULL);
|
||||||
|
return (*tid == NULL) ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pthread_join(pthread_t tid, void **retval)
|
||||||
|
{
|
||||||
|
(void)retval;
|
||||||
|
WaitForSingleObject(tid, INFINITE);
|
||||||
|
CloseHandle(tid);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pthread_cancel(pthread_t tid)
|
||||||
|
{
|
||||||
|
TerminateThread(tid, 0U);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No-op: allows test_control_thread_entry to reach exit(test_control_failed_tests). */
|
||||||
|
static inline void pthread_exit(void *retval)
|
||||||
|
{
|
||||||
|
(void)retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pthread_setcancelstate(int state, int *oldstate)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
if (oldstate != NULL)
|
||||||
|
{
|
||||||
|
*oldstate = PTHREAD_CANCEL_ENABLE;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pthread_setcanceltype(int type, int *oldtype)
|
||||||
|
{
|
||||||
|
(void)type;
|
||||||
|
if (oldtype != NULL)
|
||||||
|
{
|
||||||
|
*oldtype = PTHREAD_CANCEL_DEFERRED;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* WIN64_COMPAT_PTHREAD_H */
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
/* Copyright (c) 2025 Contributors to the Eclipse Foundation */
|
||||||
|
/* AI-generated with GitHub Copilot */
|
||||||
|
|
||||||
|
/* Minimal POSIX unistd shim for FileX regression tests on Windows/x64. */
|
||||||
|
|
||||||
|
#ifndef WIN64_COMPAT_UNISTD_H
|
||||||
|
#define WIN64_COMPAT_UNISTD_H
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
typedef unsigned int useconds_t;
|
||||||
|
|
||||||
|
/* Sleep for `usec` microseconds. Windows resolution is ~1 ms; round up. */
|
||||||
|
static inline int usleep(useconds_t usec)
|
||||||
|
{
|
||||||
|
DWORD ms = (DWORD)((usec + 999U) / 1000U);
|
||||||
|
Sleep((ms > 0U) ? ms : 1U);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* WIN64_COMPAT_UNISTD_H */
|
||||||
Reference in New Issue
Block a user