[libcamera-devel,v4,1/6] libcamera: utils: Add a C++ dirname implementation

Message ID 20200221163130.4791-2-kieran.bingham@ideasonboard.com
State Accepted
Headers show
Series
  • Support loading IPAs from the build tree
Related show

Commit Message

Kieran Bingham Feb. 21, 2020, 4:31 p.m. UTC
Provide a std::string based implementation which conforms to the
behaviour of the dirname() fucntion defined by POSIX.

Tests are added to cover expected corner cases of the implementation.

Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
---
 src/libcamera/include/utils.h |  1 +
 src/libcamera/utils.cpp       | 48 +++++++++++++++++++++++++++++++
 test/utils.cpp                | 54 +++++++++++++++++++++++++++++++++++
 3 files changed, 103 insertions(+)

Comments

Laurent Pinchart Feb. 22, 2020, 10:27 a.m. UTC | #1
Hi Kieran,

Thank you for the patch.

On Fri, Feb 21, 2020 at 04:31:25PM +0000, Kieran Bingham wrote:
> Provide a std::string based implementation which conforms to the
> behaviour of the dirname() fucntion defined by POSIX.
> 
> Tests are added to cover expected corner cases of the implementation.
> 
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> ---
>  src/libcamera/include/utils.h |  1 +
>  src/libcamera/utils.cpp       | 48 +++++++++++++++++++++++++++++++
>  test/utils.cpp                | 54 +++++++++++++++++++++++++++++++++++
>  3 files changed, 103 insertions(+)
> 
> diff --git a/src/libcamera/include/utils.h b/src/libcamera/include/utils.h
> index 080ea6614de0..940597760ee2 100644
> --- a/src/libcamera/include/utils.h
> +++ b/src/libcamera/include/utils.h
> @@ -33,6 +33,7 @@ namespace utils {
>  const char *basename(const char *path);
>  
>  char *secure_getenv(const char *name);
> +std::string dirname(const std::string &path);
>  
>  template<class InputIt1, class InputIt2>
>  unsigned int set_overlap(InputIt1 first1, InputIt1 last1,
> diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp
> index 453e3b3b5995..f566e88cec5b 100644
> --- a/src/libcamera/utils.cpp
> +++ b/src/libcamera/utils.cpp
> @@ -70,6 +70,54 @@ char *secure_getenv(const char *name)
>  #endif
>  }
>  
> +/**
> + * \brief Identify the dirname portion of a path
> + * \param[in] path The full path to parse
> + *
> + * This function conforms with the behaviour of the %dirname() function as
> + * defined by POSIX.
> + *
> + * \return A string of the directory component of the path
> + */
> +std::string dirname(const std::string &path)
> +{
> +	if (path.empty())
> +		return ".";
> +
> +	/*
> +	 * Skip all trailing slashes. If the path is only made of slashes,
> +	 * return "/".
> +	 */
> +	size_t pos = path.size() - 1;
> +	while (path[pos] == '/') {
> +		if (!pos)
> +			return "/";
> +		pos--;
> +	}
> +
> +	/*
> +	 * Find the previous slash. If the path contains no non-trailing slash,
> +	 * return ".".
> +	 */
> +	while (path[pos] != '/') {
> +		if (!pos)
> +			return ".";
> +		pos--;
> +	}
> +
> +	/*
> +	 * Return the directory name up to (but not including) any trailing
> +	 * slash. If this would result in an empty string, return "/".
> +	 */
> +	while (path[pos] == '/') {
> +		if (!pos)
> +			return "/";
> +		pos--;
> +	}
> +
> +	return path.substr(0, pos + 1);
> +}
> +
>  /**
>   * \fn libcamera::utils::set_overlap(InputIt1 first1, InputIt1 last1,
>   *				     InputIt2 first2, InputIt2 last2)
> diff --git a/test/utils.cpp b/test/utils.cpp
> index db1fbdde847d..e4184e39ce32 100644
> --- a/test/utils.cpp
> +++ b/test/utils.cpp
> @@ -19,6 +19,56 @@ using namespace libcamera;
>  class UtilsTest : public Test
>  {
>  protected:
> +	int testDirname()
> +	{
> +		std::vector<std::string> paths = {

You can make this const.

> +			"",
> +			"///",
> +			"/bin",
> +			"/usr/bin",
> +			"//etc////",
> +			"//tmp//d//",
> +			"current_file",
> +			"./current_file",
> +			"./current_dir/",
> +			"current_dir/",
> +		};
> +
> +		std::vector<std::string> expected = {

And this too.

> +			".",
> +			"/",
> +			"/",
> +			"/usr",
> +			"/",
> +			"//tmp",
> +			".",
> +			".",
> +			".",
> +			".",
> +		};

I was going to propose using the libc-provided dirname() function to
check for correctness instead of hardcoding the results, but that would
create a dependency on the libc implementation conforming with POSIX,
which may not be guaranteed on non-glibc platforms. Let's thus keep it
as-is.

> +
> +		std::vector<std::string> results;
> +
> +		for (const auto &path : paths)
> +			results.push_back(utils::dirname(path));
> +
> +		if (results != expected) {
> +			cerr << "utils::dirname() tests failed" << endl;
> +
> +			cerr << "expected: " << endl;
> +			for (const auto &path : expected)
> +				cerr << " " << path << endl;

"\t" instead of " " for additional clarity ?

> +
> +			cerr << "results: " << endl;
> +			for (const auto &path : results)
> +				cerr << " " << path << endl;

Here too ?

> +
> +			return TestFail;
> +		}
> +
> +		return 0;

		return TestPass;

> +	}
> +
>  	int run()
>  	{
>  		/* utils::hex() test. */
> @@ -71,6 +121,10 @@ protected:
>  			return TestFail;
>  		}
>  
> +		/* utils::dirname() tests. */
> +		if (testDirname())

		if (testDirname() != TestPass)

?

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +			return TestFail;
> +
>  		return TestPass;
>  	}
>  };
Kieran Bingham Feb. 24, 2020, 9:36 a.m. UTC | #2
Hi Laurent,

On 22/02/2020 10:27, Laurent Pinchart wrote:
> Hi Kieran,
> 
> Thank you for the patch.
> 
> On Fri, Feb 21, 2020 at 04:31:25PM +0000, Kieran Bingham wrote:
>> Provide a std::string based implementation which conforms to the
>> behaviour of the dirname() fucntion defined by POSIX.
>>
>> Tests are added to cover expected corner cases of the implementation.
>>
>> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
>> ---
>>  src/libcamera/include/utils.h |  1 +
>>  src/libcamera/utils.cpp       | 48 +++++++++++++++++++++++++++++++
>>  test/utils.cpp                | 54 +++++++++++++++++++++++++++++++++++
>>  3 files changed, 103 insertions(+)
>>
>> diff --git a/src/libcamera/include/utils.h b/src/libcamera/include/utils.h
>> index 080ea6614de0..940597760ee2 100644
>> --- a/src/libcamera/include/utils.h
>> +++ b/src/libcamera/include/utils.h
>> @@ -33,6 +33,7 @@ namespace utils {
>>  const char *basename(const char *path);
>>  
>>  char *secure_getenv(const char *name);
>> +std::string dirname(const std::string &path);
>>  
>>  template<class InputIt1, class InputIt2>
>>  unsigned int set_overlap(InputIt1 first1, InputIt1 last1,
>> diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp
>> index 453e3b3b5995..f566e88cec5b 100644
>> --- a/src/libcamera/utils.cpp
>> +++ b/src/libcamera/utils.cpp
>> @@ -70,6 +70,54 @@ char *secure_getenv(const char *name)
>>  #endif
>>  }
>>  
>> +/**
>> + * \brief Identify the dirname portion of a path
>> + * \param[in] path The full path to parse
>> + *
>> + * This function conforms with the behaviour of the %dirname() function as
>> + * defined by POSIX.
>> + *
>> + * \return A string of the directory component of the path
>> + */
>> +std::string dirname(const std::string &path)
>> +{
>> +	if (path.empty())
>> +		return ".";
>> +
>> +	/*
>> +	 * Skip all trailing slashes. If the path is only made of slashes,
>> +	 * return "/".
>> +	 */
>> +	size_t pos = path.size() - 1;
>> +	while (path[pos] == '/') {
>> +		if (!pos)
>> +			return "/";
>> +		pos--;
>> +	}
>> +
>> +	/*
>> +	 * Find the previous slash. If the path contains no non-trailing slash,
>> +	 * return ".".
>> +	 */
>> +	while (path[pos] != '/') {
>> +		if (!pos)
>> +			return ".";
>> +		pos--;
>> +	}
>> +
>> +	/*
>> +	 * Return the directory name up to (but not including) any trailing
>> +	 * slash. If this would result in an empty string, return "/".
>> +	 */
>> +	while (path[pos] == '/') {
>> +		if (!pos)
>> +			return "/";
>> +		pos--;
>> +	}
>> +
>> +	return path.substr(0, pos + 1);
>> +}
>> +
>>  /**
>>   * \fn libcamera::utils::set_overlap(InputIt1 first1, InputIt1 last1,
>>   *				     InputIt2 first2, InputIt2 last2)
>> diff --git a/test/utils.cpp b/test/utils.cpp
>> index db1fbdde847d..e4184e39ce32 100644
>> --- a/test/utils.cpp
>> +++ b/test/utils.cpp
>> @@ -19,6 +19,56 @@ using namespace libcamera;
>>  class UtilsTest : public Test
>>  {
>>  protected:
>> +	int testDirname()
>> +	{
>> +		std::vector<std::string> paths = {
> 
> You can make this const.

Ah yes

> 
>> +			"",
>> +			"///",
>> +			"/bin",
>> +			"/usr/bin",
>> +			"//etc////",
>> +			"//tmp//d//",
>> +			"current_file",
>> +			"./current_file",
>> +			"./current_dir/",
>> +			"current_dir/",
>> +		};
>> +
>> +		std::vector<std::string> expected = {
> 
> And this too.
> 
>> +			".",
>> +			"/",
>> +			"/",
>> +			"/usr",
>> +			"/",
>> +			"//tmp",
>> +			".",
>> +			".",
>> +			".",
>> +			".",
>> +		};
> 
> I was going to propose using the libc-provided dirname() function to
> check for correctness instead of hardcoding the results, but that would
> create a dependency on the libc implementation conforming with POSIX,
> which may not be guaranteed on non-glibc platforms. Let's thus keep it
> as-is.

Indeed, I wanted direct expected results hardcoded for validation.

I also liked your utils::split test implementation, and I was tempted to
make this a vector of struct { char * path, char * expected } , but I
just went with this for speed, and because it's nice to show the whole
results in the event of a failure.


>> +
>> +		std::vector<std::string> results;
>> +
>> +		for (const auto &path : paths)
>> +			results.push_back(utils::dirname(path));
>> +
>> +		if (results != expected) {
>> +			cerr << "utils::dirname() tests failed" << endl;
>> +
>> +			cerr << "expected: " << endl;
>> +			for (const auto &path : expected)
>> +				cerr << " " << path << endl;
> 
> "\t" instead of " " for additional clarity ?

Sure, Mostly this code came in useful while I was fixing the
implementation so I /hope/ it will never be broken, and thus this code
shouldn't ever print (unless someone extends the tss?)

> 
>> +
>> +			cerr << "results: " << endl;
>> +			for (const auto &path : results)
>> +				cerr << " " << path << endl;
> 
> Here too ?
> 
>> +
>> +			return TestFail;
>> +		}
>> +
>> +		return 0;
> 
> 		return TestPass;
> 
>> +	}
>> +
>>  	int run()
>>  	{
>>  		/* utils::hex() test. */
>> @@ -71,6 +121,10 @@ protected:
>>  			return TestFail;
>>  		}
>>  
>> +		/* utils::dirname() tests. */
>> +		if (testDirname())
> 
> 		if (testDirname() != TestPass)
> 
> ?


Yes, that would be better in case the TestPass/TestFail values ever gets
changed underneath...

> 
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>


Thanks, fixups applied.


> 
>> +			return TestFail;
>> +
>>  		return TestPass;
>>  	}
>>  };
>

Patch

diff --git a/src/libcamera/include/utils.h b/src/libcamera/include/utils.h
index 080ea6614de0..940597760ee2 100644
--- a/src/libcamera/include/utils.h
+++ b/src/libcamera/include/utils.h
@@ -33,6 +33,7 @@  namespace utils {
 const char *basename(const char *path);
 
 char *secure_getenv(const char *name);
+std::string dirname(const std::string &path);
 
 template<class InputIt1, class InputIt2>
 unsigned int set_overlap(InputIt1 first1, InputIt1 last1,
diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp
index 453e3b3b5995..f566e88cec5b 100644
--- a/src/libcamera/utils.cpp
+++ b/src/libcamera/utils.cpp
@@ -70,6 +70,54 @@  char *secure_getenv(const char *name)
 #endif
 }
 
+/**
+ * \brief Identify the dirname portion of a path
+ * \param[in] path The full path to parse
+ *
+ * This function conforms with the behaviour of the %dirname() function as
+ * defined by POSIX.
+ *
+ * \return A string of the directory component of the path
+ */
+std::string dirname(const std::string &path)
+{
+	if (path.empty())
+		return ".";
+
+	/*
+	 * Skip all trailing slashes. If the path is only made of slashes,
+	 * return "/".
+	 */
+	size_t pos = path.size() - 1;
+	while (path[pos] == '/') {
+		if (!pos)
+			return "/";
+		pos--;
+	}
+
+	/*
+	 * Find the previous slash. If the path contains no non-trailing slash,
+	 * return ".".
+	 */
+	while (path[pos] != '/') {
+		if (!pos)
+			return ".";
+		pos--;
+	}
+
+	/*
+	 * Return the directory name up to (but not including) any trailing
+	 * slash. If this would result in an empty string, return "/".
+	 */
+	while (path[pos] == '/') {
+		if (!pos)
+			return "/";
+		pos--;
+	}
+
+	return path.substr(0, pos + 1);
+}
+
 /**
  * \fn libcamera::utils::set_overlap(InputIt1 first1, InputIt1 last1,
  *				     InputIt2 first2, InputIt2 last2)
diff --git a/test/utils.cpp b/test/utils.cpp
index db1fbdde847d..e4184e39ce32 100644
--- a/test/utils.cpp
+++ b/test/utils.cpp
@@ -19,6 +19,56 @@  using namespace libcamera;
 class UtilsTest : public Test
 {
 protected:
+	int testDirname()
+	{
+		std::vector<std::string> paths = {
+			"",
+			"///",
+			"/bin",
+			"/usr/bin",
+			"//etc////",
+			"//tmp//d//",
+			"current_file",
+			"./current_file",
+			"./current_dir/",
+			"current_dir/",
+		};
+
+		std::vector<std::string> expected = {
+			".",
+			"/",
+			"/",
+			"/usr",
+			"/",
+			"//tmp",
+			".",
+			".",
+			".",
+			".",
+		};
+
+		std::vector<std::string> results;
+
+		for (const auto &path : paths)
+			results.push_back(utils::dirname(path));
+
+		if (results != expected) {
+			cerr << "utils::dirname() tests failed" << endl;
+
+			cerr << "expected: " << endl;
+			for (const auto &path : expected)
+				cerr << " " << path << endl;
+
+			cerr << "results: " << endl;
+			for (const auto &path : results)
+				cerr << " " << path << endl;
+
+			return TestFail;
+		}
+
+		return 0;
+	}
+
 	int run()
 	{
 		/* utils::hex() test. */
@@ -71,6 +121,10 @@  protected:
 			return TestFail;
 		}
 
+		/* utils::dirname() tests. */
+		if (testDirname())
+			return TestFail;
+
 		return TestPass;
 	}
 };