Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion ff_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -512,14 +512,58 @@ static FF_FILE * prvAllocFileHandle( FF_IOManager_t * pxIOManager,
FF_DirEnt_t xDirEntry;
FF_Error_t xError = FF_ERR_NONE;
BaseType_t xReturn;
uint32_t ulDirCluster;
uint16_t usPathLength;

if( pxIOManager == NULL )
{
xReturn = pdFALSE;
}
else
{
xError = FF_FindFirst( pxIOManager, &xDirEntry, pcPath );
memset( &xDirEntry, 0, sizeof( xDirEntry ) );

#if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
usPathLength = ( uint16_t ) wcslen( pcPath );
#else
usPathLength = ( uint16_t ) strlen( pcPath );
#endif

#if ( ffconfigREMOVABLE_MEDIA != 0 )
if( ( pxIOManager->ucFlags & FF_IOMAN_DEVICE_IS_EXTRACTED ) != 0 )
{
xError = FF_createERR( FF_ERR_IOMAN_DRIVER_NOMEDIUM, FF_ISDIREMPTY );
}
else
#endif /* ffconfigREMOVABLE_MEDIA */
{
/* FF_isDirEmpty() must enumerate the directory contents. Calling
* FF_FindFirst() would let wildcard mode reinterpret a path without
* a trailing separator as the directory entry itself. */
ulDirCluster = FF_FindDir( pxIOManager, pcPath, usPathLength, &xError );

if( FF_isERR( xError ) == pdFALSE )
{
if( ulDirCluster == 0UL )
{
/* FAT12/FAT16 mounts represent their fixed root directory
* with cluster 1, so zero remains the lookup-failure
* sentinel for every FAT type. */
xError = FF_createERR( FF_ERR_DIR_INVALID_PATH, FF_ISDIREMPTY );
}
else
{
xDirEntry.ulDirCluster = ulDirCluster;
xError = FF_InitEntryFetch( pxIOManager, ulDirCluster, &( xDirEntry.xFetchContext ) );

if( FF_isERR( xError ) == pdFALSE )
{
xDirEntry.usCurrentItem = 0;
xError = FF_FindNext( pxIOManager, &xDirEntry );
}
}
}
}

/* Assume the directory is empty until a file is
* encountered with a name other than ".." or "." */
Expand Down
59 changes: 58 additions & 1 deletion test/unit-test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,70 @@ create_test( ${utest_name}
"${utest_link_list}"
"${utest_dep_list}"
"${test_include_directories}" )
set( coverage_dep_list ${utest_name} )

set( project_name "ff_file_dir_empty" )

# ===================== Mocks ================================================
# Keep this unit focused on FF_isDirEmpty() and discard unrelated ff_file APIs.
set( mock_list "" )
list( APPEND mock_list
${UNIT_TEST_DIR}/include/ff_dir_is_empty_mock_subset.h )

set( mock_include_list ${FAT_TEST_INCLUDE_DIRS} )
set( mock_define_list "" )

# ================== Module under test =======================================
set( real_source_files "" )
list( APPEND real_source_files
${MODULE_ROOT_DIR}/ff_file.c )

set( real_include_directories ${FAT_TEST_INCLUDE_DIRS} )

# ===================== Test code ============================================
set( test_include_directories ${FAT_TEST_INCLUDE_DIRS} )

# ==============================================================================
set( mock_name "${project_name}_mock" )
set( real_name "${project_name}_real" )

create_mock_list( ${mock_name}
"${mock_list}"
"${MODULE_ROOT_DIR}/tools/cmock/project.yml"
"${mock_include_list}"
"${mock_define_list}" )

create_real_library( ${real_name}
"${real_source_files}"
"${real_include_directories}"
"${mock_name}" )
target_compile_options( ${real_name} PRIVATE -ffunction-sections -fdata-sections )

set( utest_dep_list "" )
list( APPEND utest_dep_list ${real_name} )

set( utest_link_list "" )
list( APPEND utest_link_list
lib${real_name}.a
-l${mock_name}
-Wl,--gc-sections )

set( utest_name "${project_name}_utest" )
set( utest_source "${UNIT_TEST_DIR}/ff_file_dir_empty_utest.c" )

create_test( ${utest_name}
${utest_source}
"${utest_link_list}"
"${utest_dep_list}"
"${test_include_directories}" )
list( APPEND coverage_dep_list ${utest_name} )

# ------------------------------------------------------------------------------
# `coverage` target: run the tests and collect lcov data into coverage.info.
# ------------------------------------------------------------------------------
add_custom_target( coverage
COMMAND ${CMAKE_COMMAND} -DCMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}
-P ${MODULE_ROOT_DIR}/tools/cmock/coverage.cmake
DEPENDS ${utest_name}
DEPENDS ${coverage_dep_list}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Running unit tests and collecting coverage" )
107 changes: 107 additions & 0 deletions test/unit-test/ff_file_dir_empty_utest.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Unit tests for directory-empty checks in ff_file.c.
*
* SPDX-License-Identifier: MIT
*/

#include <stdint.h>
#include <string.h>

#include "unity.h"

#include "mock_ff_dir_is_empty_mock_subset.h"

#include "ff_headers.h"

#define TEST_ROOT_DIR_CLUSTER ( 1U )
#define TEST_SUBDIR_CLUSTER ( 7U )

static FF_IOManager_t xIOManager;

static BaseType_t prvCheckEmptyDirectory( const char * pcPath,
uint32_t ulDirCluster )
{
FF_Error_t xFindError = FF_ERR_NONE;

FF_FindDir_ExpectAndReturn( &xIOManager,
pcPath,
( uint16_t ) strlen( pcPath ),
NULL,
ulDirCluster );
FF_FindDir_IgnoreArg_pxError();
FF_FindDir_ReturnThruPtr_pxError( &xFindError );
FF_InitEntryFetch_ExpectAndReturn( &xIOManager,
ulDirCluster,
NULL,
FF_ERR_NONE );
FF_InitEntryFetch_IgnoreArg_pxContext();
FF_FindNext_ExpectAndReturn( &xIOManager,
NULL,
FF_createERR( FF_ERR_DIR_END_OF_DIR, FF_FINDNEXT ) );
FF_FindNext_IgnoreArg_pxDirEntry();

return FF_isDirEmpty( &xIOManager, pcPath );
}

void setUp( void )
{
memset( &xIOManager, 0, sizeof( xIOManager ) );
}

void tearDown( void )
{
}

void test_FF_isDirEmpty_accepts_FAT12_root_directory_cluster( void )
{
xIOManager.xPartition.ucType = FF_T_FAT12;
xIOManager.xPartition.ulRootDirCluster = TEST_ROOT_DIR_CLUSTER;

TEST_ASSERT_EQUAL( pdTRUE,
prvCheckEmptyDirectory( "/", TEST_ROOT_DIR_CLUSTER ) );
}

void test_FF_isDirEmpty_accepts_FAT16_root_directory_cluster( void )
{
xIOManager.xPartition.ucType = FF_T_FAT16;
xIOManager.xPartition.ulRootDirCluster = TEST_ROOT_DIR_CLUSTER;

TEST_ASSERT_EQUAL( pdTRUE,
prvCheckEmptyDirectory( "/", TEST_ROOT_DIR_CLUSTER ) );
}

void test_FF_isDirEmpty_handles_directory_with_or_without_trailing_separator( void )
{
TEST_ASSERT_EQUAL( pdTRUE,
prvCheckEmptyDirectory( "/dir", TEST_SUBDIR_CLUSTER ) );
TEST_ASSERT_EQUAL( pdTRUE,
prvCheckEmptyDirectory( "/dir/", TEST_SUBDIR_CLUSTER ) );
}

void test_FF_isDirEmpty_reports_directory_entry_as_non_empty( void )
{
FF_DirEnt_t xDirEntry;
FF_Error_t xFindError = FF_ERR_NONE;
const char * pcPath = "/dir";

memset( &xDirEntry, 0, sizeof( xDirEntry ) );
( void ) strcpy( xDirEntry.pcFileName, "file.txt" );

FF_FindDir_ExpectAndReturn( &xIOManager,
pcPath,
( uint16_t ) strlen( pcPath ),
NULL,
TEST_SUBDIR_CLUSTER );
FF_FindDir_IgnoreArg_pxError();
FF_FindDir_ReturnThruPtr_pxError( &xFindError );
FF_InitEntryFetch_ExpectAndReturn( &xIOManager,
TEST_SUBDIR_CLUSTER,
NULL,
FF_ERR_NONE );
FF_InitEntryFetch_IgnoreArg_pxContext();
FF_FindNext_ExpectAndReturn( &xIOManager, NULL, FF_ERR_NONE );
FF_FindNext_IgnoreArg_pxDirEntry();
FF_FindNext_ReturnThruPtr_pxDirEntry( &xDirEntry );

TEST_ASSERT_EQUAL( pdFALSE, FF_isDirEmpty( &xIOManager, pcPath ) );
}
20 changes: 20 additions & 0 deletions test/unit-test/include/ff_dir_is_empty_mock_subset.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Minimal dependency subset for FF_isDirEmpty() unit tests.
*
* SPDX-License-Identifier: MIT
*/

#ifndef FF_DIR_IS_EMPTY_MOCK_SUBSET_H
#define FF_DIR_IS_EMPTY_MOCK_SUBSET_H

uint32_t FF_FindDir( FF_IOManager_t * pxIOManager,
const char * pcPath,
uint16_t usPathLength,
FF_Error_t * pxError );
FF_Error_t FF_InitEntryFetch( FF_IOManager_t * pxIOManager,
uint32_t ulDirCluster,
FF_FetchContext_t * pxContext );
FF_Error_t FF_FindNext( FF_IOManager_t * pxIOManager,
FF_DirEnt_t * pxDirEntry );

#endif /* FF_DIR_IS_EMPTY_MOCK_SUBSET_H */
Loading