当前方案是解决container部署下,非root用户下添加user/group功能,比较极端,后来大专家给出了另外的方案,当前极端方案被否,UT方面比较有代表性,可以作为样例,说不定以后用得上
C:.
│ Makefile.am
│
└─userprofileconfig
├─include
│ sysFuncInterface.hpp
│ sysFuncInterfaceImpl.hpp
│ userprofileconfig.hpp
│
├─src
│ main.cpp
│ userprofileconfig.cpp
│
└─tst
userprofileconfigTest.cpp
userprofilesystemMock.hpp
userprofileconfig.hpp
#ifndef YOUR_OWN_USERPROFILECONFIG_HPP
#define YOUR_OWN_USERPROFILECONFIG_HPP
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sysFuncInterface.hpp>
class userProfileConfig
{
static constexpr auto USRPROFILE_READ_WRITE = "ab+";
static constexpr auto USRPROFILE_FILENAME_GROUP = "/etc/group";
static constexpr auto USRPROFILE_FILENAME_PWD = "/etc/passwd";
static constexpr auto USRPROFILE_USER_NAME = "YOUR_OWN";
static constexpr auto USRPROFILE_PASSWD_STRING = "%s:x:%d:%d::/home/YOUR_OWN:/bin/bash\n";
static constexpr auto USRPROFILE_GROUP_STRING = "%s:x:%d:\n";
static const int USRPROFILECONFIG_OK = 0;
static const int USRPROFILECONFIG_NOK = 1;
public:
userProfileConfig(){}
~userProfileConfig(){}
int updateGroupAndUsr(sysFuncInterface* sysFuncImplimentation);
private:
int addUser(FILE *fp_password, const char* userName);
int updateUserName(FILE *fp_password);
int addGroup(FILE *fp_group, const char* userName);
int updateGroupName(FILE *fp_group);
sysFuncInterface* sysFuncImpl;
};
#endif //YOUR_OWN_USERPROFILECONFIG_HPP
sysFuncInterface.hpp
#ifndef YOUR_OWN_SYSFUNCINTERFACE_HPP
#define YOUR_OWN_SYSFUNCINTERFACE_HPP
#include<grp.h>
#include<sys/types.h>
#include<unistd.h>
#include <pwd.h>
class sysFuncInterface {
public:
virtual ~sysFuncInterface() {}
virtual FILE* FileOpen(const char* filename, const char* mode) = 0;
virtual size_t FileWrite(const void* data, size_t size, size_t num, FILE* file) = 0;
virtual int FileClose(FILE* file) = 0;
virtual uid_t Getuid() = 0;
virtual uid_t Geteuid() = 0;
virtual gid_t Getgid() = 0;
virtual struct group * Getgrgid(gid_t gid) = 0;
virtual struct passwd *Getpwnam(const char *name) = 0;
};
#endif //YOUR_OWN_SYSFUNCINTERFACE_HPP
sysFuncInterfaceImpl.hpp
#ifndef YOUR_OWN_SYSFUNCINTERFACEIMPL_HPP
#define YOUR_OWN_SYSFUNCINTERFACEIMPL_HPP
#include<sys/types.h>
#include<unistd.h>
#include <sysFuncInterface.hpp>
class sysFuncInterfaceImpl : public sysFuncInterface
{
public:
virtual FILE* FileOpen(const char* filename, const char* mode)
{
return fopen(filename, mode);
}
virtual size_t FileWrite(const void* data, size_t size, size_t num, FILE* file)
{
return fwrite(data, size, num, file);
}
virtual int FileClose(FILE* file)
{
return fclose(file);
}
virtual uid_t Getuid(void)
{
return getuid();
}
virtual uid_t Geteuid(void)
{
return geteuid();
}
virtual gid_t Getgid(void)
{
return getgid();
}
virtual struct group * Getgrgid(gid_t gid)
{
return getgrgid(gid);
}
virtual struct passwd *Getpwnam(const char *name)
{
return getpwnam(name);
}
};
#endif //YOUR_OWN_SYSFUNCINTERFACEIMPL_HPP
userprofileconfig.cpp
#include <cstring>
#include <cerrno>
#include <common/include/log.h>
#include <userprofileconfig.hpp>
int userProfileConfig::addUser(FILE *fp_password, const char* userName)
{
int ret = USRPROFILECONFIG_OK;
char buffer[256] = {0};
snprintf(buffer, sizeof(buffer), USRPROFILE_PASSWD_STRING, userName, sysFuncImpl->Getuid(), sysFuncImpl->Getgid() );
if(sysFuncImpl->FileWrite(buffer, sizeof(char), strlen(buffer), fp_password) != (strlen(buffer)))
{
TRS_ERR("failed to write, line %d, len %ld, error[%s].\n", __LINE__, strlen(buffer), strerror(errno));
ret = USRPROFILECONFIG_NOK;
}
return ret;
}
int userProfileConfig::updateUserName(FILE *fp_password)
{
struct passwd *pwdInfo = NULL;
pwdInfo = sysFuncImpl->Getpwnam(USRPROFILE_USER_NAME);
if(NULL == pwdInfo)
{
return addUser(fp_password, USRPROFILE_USER_NAME);
}
return USRPROFILECONFIG_OK;
}
int userProfileConfig::addGroup(FILE *fp_group, const char* userName)
{
int ret = USRPROFILECONFIG_OK;
char buffer[256] = {0};
snprintf(buffer, sizeof(buffer), USRPROFILE_GROUP_STRING, userName, sysFuncImpl->Getgid() );
if(sysFuncImpl->FileWrite(buffer, sizeof(char), strlen(buffer), fp_group) != (strlen(buffer)))
{
TRS_ERR("failed to write, line %d, len %ld, error[%s].\n", __LINE__, strlen(buffer), strerror(errno));
ret = USRPROFILECONFIG_NOK;
}
return ret;
}
int userProfileConfig::updateGroupName(FILE *fp_group)
{
struct group *grp = sysFuncImpl->Getgrgid(sysFuncImpl->Getgid());
if (NULL == grp)
{
return addGroup(fp_group, USRPROFILE_USER_NAME);
}
return USRPROFILECONFIG_OK;
}
int userProfileConfig::updateGroupAndUsr(sysFuncInterface* sysFuncImplimentation)
{
sysFuncImpl = sysFuncImplimentation;
//do nothing if root
if(sysFuncImpl->Geteuid() == 0)
{
return 0;
}
int ret;
FILE *fp_group = NULL;
FILE *fp_password = NULL;
fp_group = sysFuncImpl->FileOpen(USRPROFILE_FILENAME_GROUP, USRPROFILE_READ_WRITE);
if (fp_group == NULL)
{
TRS_ERR("failed to open file, line:%d, error[%s].\n", __LINE__, strerror(errno));
ret = USRPROFILECONFIG_NOK;
}
else
{
fp_password = sysFuncImpl->FileOpen(USRPROFILE_FILENAME_PWD, USRPROFILE_READ_WRITE);
if (fp_password == NULL)
{
TRS_ERR("failed to open file, line:%d, error[%s].\n", __LINE__, strerror(errno));
ret = USRPROFILECONFIG_NOK;
}
}
if(fp_group && fp_password)
{
//check if group exist, if not, create and add to /etc/group
ret = updateGroupName(fp_group);
//check if uid exist, if not, create and add to /etc/passwd
ret += updateUserName(fp_password);
}
if(fp_group)
{
sysFuncImpl->FileClose(fp_group);
}
if(fp_password)
{
sysFuncImpl->FileClose(fp_password);
}
return ret;
};
main.cpp
#include <stdio.h>
#include <sysFuncInterfaceImpl.hpp>
#include "userprofileconfig.hpp"
int main(int argc,char** argv)
{
userProfileConfig userprofileconfig;
sysFuncInterfaceImpl sysFuncImpl;
return userprofileconfig.updateGroupAndUsr((sysFuncInterface*)&sysFuncImpl);
}
跳过方案本身的讨论,在实现层面,调用了不少系统函数,如何做好UT呢?更具体的说,如何更容易mock 系统函数, 其实上面已经体现了方案。
UT 样例如下
userprofilesystemMock.hpp
#ifndef YOUR_OWN_USERPROFILESYSTEMMOCK_HPP
#define YOUR_OWN_USERPROFILESYSTEMMOCK_HPP
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <cstring>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <sysFuncInterface.hpp>
class userProfileSystemMock: public sysFuncInterface
{
public:
virtual ~userProfileSystemMock() { }
MOCK_METHOD0(Getuid, uid_t());
MOCK_METHOD0(Geteuid, uid_t(void));
MOCK_METHOD0(Getgid, gid_t(void));
MOCK_METHOD1(Getgrgid, struct group *(gid_t gid));
MOCK_METHOD1(Getpwnam, struct passwd *(const char *));
MOCK_METHOD2(FileOpen, FILE *( const char *filename, const char *mode));
MOCK_METHOD1(FileClose, int(FILE *fp));
MOCK_METHOD4(FileWrite, size_t(const void *ptr, size_t size, size_t nmemb, FILE *stream));
};
#endif //YOUR_OWN_USERPROFILESYSTEMMOCK_HPP
userprofileconfigTest.cpp
#include <userprofileconfig.hpp>
#include <userprofilesystemMock.hpp>
using namespace testing;
#define TEST_USERPROFILECONFIG_OK 0
class userprofileconfigTest: public ::testing::Test
{
public:
userProfileSystemMock systemMock;
userProfileConfig userprofileconfig;
protected:
void SetUp() override
{
}
void TearDown() override
{
}
FILE* pwdnull = NULL;
FILE* pwdok = (FILE*)0xFFFFFFFF;
struct passwd *pwdInfo = (struct passwd*)0xFFFFFFFF;
struct passwd *pwdInfonull = (struct passwd*)0;
struct group *grpInfo = (struct group*)0xFFFFFFFF;
struct group *grpnull = (struct group*)0;
void expectCallInit()
{
EXPECT_CALL(systemMock, Getuid()).Times(1).WillRepeatedly(Return(1));
EXPECT_CALL(systemMock, Geteuid()).Times(1).WillRepeatedly(Return(1));
EXPECT_CALL(systemMock, Getgid()).Times(3).WillRepeatedly(Return(1));
EXPECT_CALL(systemMock, Getgrgid(_)).Times(1).WillRepeatedly(Return(grpnull));
EXPECT_CALL(systemMock, FileOpen(_, _)).Times(2).WillRepeatedly(Return(pwdok));
EXPECT_CALL(systemMock, Getpwnam(_)).Times(1).WillOnce(Return(pwdInfonull));
EXPECT_CALL(systemMock, FileClose(_)).Times(2).WillRepeatedly(Return(0));
}
};
TEST_F(userprofileconfigTest, uidIsRootShouldReturnOK)
{
EXPECT_CALL(systemMock, Geteuid()).Times(1).WillOnce(Return(0));
EXPECT_EQ(TEST_USERPROFILECONFIG_OK, userprofileconfig.updateGroupAndUsr(&systemMock));
}
TEST_F(userprofileconfigTest, groupFileOpenFailedShouldReturnNOK)
{
EXPECT_CALL(systemMock, Geteuid()).Times(1).WillRepeatedly(Return(1));
EXPECT_CALL(systemMock, FileOpen(_, _)).Times(1).WillOnce(Return(pwdnull));
EXPECT_NE(TEST_USERPROFILECONFIG_OK, userprofileconfig.updateGroupAndUsr(&systemMock));
}
TEST_F(userprofileconfigTest, passwdFileOpenFailedShouldReturnNOK)
{
EXPECT_CALL(systemMock, Geteuid()).Times(1).WillRepeatedly(Return(1));
EXPECT_CALL(systemMock, FileOpen(_, _)).Times(2).WillOnce(Return(pwdok)).WillOnce(Return(pwdnull));
EXPECT_CALL(systemMock, FileClose(_)).Times(1).WillRepeatedly(Return(0));
EXPECT_NE(TEST_USERPROFILECONFIG_OK, userprofileconfig.updateGroupAndUsr(&systemMock));
}
TEST_F(userprofileconfigTest, passwdAndgroupFileWriteFailedShouldReturnNOK)
{
expectCallInit();
EXPECT_CALL(systemMock, FileWrite(_, _, _, _)).Times(2).WillRepeatedly(Return(0));
EXPECT_NE(TEST_USERPROFILECONFIG_OK, userprofileconfig.updateGroupAndUsr(&systemMock));
}
TEST_F(userprofileconfigTest, passwdAndgroupFileWriteSuccessShouldReturnOK)
{
expectCallInit();
EXPECT_CALL(systemMock, FileWrite(_, _, _, _)) .Times(2).WillOnce(Return(15)).WillOnce(Return(43));
EXPECT_EQ(TEST_USERPROFILECONFIG_OK, userprofileconfig.updateGroupAndUsr(&systemMock));
}
TEST_F(userprofileconfigTest, userIsAlreadyExistShouldReturnOK)
{
EXPECT_CALL(systemMock, Geteuid()).Times(1).WillRepeatedly(Return(1));
EXPECT_CALL(systemMock, FileOpen(_, _)).Times(2).WillRepeatedly(Return(pwdok));
EXPECT_CALL(systemMock, Getgid()).Times(1).WillRepeatedly(Return(1));
EXPECT_CALL(systemMock, Getgrgid(_)).Times(1).WillRepeatedly(Return(grpInfo));
EXPECT_CALL(systemMock, Getpwnam(_)).Times(1).WillOnce(Return(pwdInfo));
EXPECT_CALL(systemMock, FileClose(_)).Times(2).WillRepeatedly(Return(0));
EXPECT_EQ(TEST_USERPROFILECONFIG_OK, userprofileconfig.updateGroupAndUsr(&systemMock));
}
上面的mock 方式,大专家一开始开了也喊了一下: nice mock
makefile(是嵌入到一个大一些的subsystem里面,权当示例)
dist_bin_SCRIPTS = start-rcpYOUR_OWN
BASECPPFLAGS = $(PTHREAD_CFLAGS) \
-I$(top_srcdir)/config/userprofileconfig/include
BASELDFLAGS = $(PTHREAD_CFLAGS)
AM_CXXFLAGS = $(WARN_CXXFLAGS) -std=c++17
BASELIBS = $(PTHREAD_LIBS)
NAME = YOUR_OWN
userprofileinitbindir = @libexecdir@/$(NAME)
#binary
userprofileinitbin_PROGRAMS =\
userprofileinit
userprofileinit_SOURCES = \
userprofileconfig/src/userprofileconfig.cpp \
userprofileconfig/src/main.cpp
userprofileinit_CPPFLAGS = $(BASECPPFLAGS)
userprofileinit_LDFLAGS = $(BASELDFLAGS)
userprofileinit_LDADD = $(BASELIBS)
####################################################################
######## UNIT Test #################################################
check_PROGRAMS = \
testuserprofileinit
COMMONUT_LIBS = \
$(PTHREAD_LIBS) \
$(CODE_COVERAGE_LIBS) \
$(GTEST_LIBS) \
$(GMOCK_LIBS)
testuserprofileinit_SOURCES = \
userprofileconfig/src/userprofileconfig.cpp \
userprofileconfig/tst/userprofileconfigTest.cpp
testuserprofileinit_CPPFLAGS = \
$(BASE_NO_WARNING_FLAGS) \
-I$(top_srcdir)/config/userprofileconfig/include \
-I$(top_srcdir)/config/userprofileconfig/tst
testuserprofileinit_CXXFLAGS = $(WARN_CXXFLAGS) $(CODE_COVERAGE_CXXFLAGS)
testuserprofileinit_LDFLAGS = $(PTHREAD_LDFLAGS)
testuserprofileinit_LDADD = \
$(COMMONUT_LIBS)
TESTS = \
testuserprofileinit