From c3e92a51d744bc8420e0626b06ee3a0efd030341 Mon Sep 17 00:00:00 2001 From: xuri Date: Fri, 14 Aug 2020 16:09:50 +0000 Subject: [PATCH] Compatible with Go 1.15, fix unit test failed on Windows and fixed #689 potential race condition --- .travis.yml | 2 +- cell.go | 20 +++++++------------- cell_test.go | 21 +++++++++++++++++++++ excelize.go | 6 +++++- lib.go | 2 +- rows.go | 4 ++++ stream_test.go | 1 + xmlWorksheet.go | 6 +++++- 8 files changed, 45 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 03825a80..a5c55f3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ env: script: - env GO111MODULE=on go vet ./... - - env GO111MODULE=on go test ./... -v -coverprofile=coverage.txt -covermode=atomic + - env GO111MODULE=on go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/cell.go b/cell.go index 3293d19f..383c02cf 100644 --- a/cell.go +++ b/cell.go @@ -18,7 +18,6 @@ import ( "reflect" "strconv" "strings" - "sync" "time" ) @@ -33,8 +32,6 @@ const ( STCellFormulaTypeShared = "shared" ) -var rwMutex sync.RWMutex - // GetCellValue provides a function to get formatted value from cell by given // worksheet name and axis in XLSX file. If it is possible to apply a format // to the cell value, it will do so, if not then an error will be returned, @@ -181,8 +178,6 @@ func setCellDuration(value time.Duration) (t string, v string) { // SetCellInt provides a function to set int type value of a cell by given // worksheet name, cell coordinates and cell value. func (f *File) SetCellInt(sheet, axis string, value int) error { - rwMutex.Lock() - defer rwMutex.Unlock() xlsx, err := f.workSheetReader(sheet) if err != nil { return err @@ -204,8 +199,6 @@ func setCellInt(value int) (t string, v string) { // SetCellBool provides a function to set bool type value of a cell by given // worksheet name, cell name and cell value. func (f *File) SetCellBool(sheet, axis string, value bool) error { - rwMutex.Lock() - defer rwMutex.Unlock() xlsx, err := f.workSheetReader(sheet) if err != nil { return err @@ -239,8 +232,6 @@ func setCellBool(value bool) (t string, v string) { // f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32) // func (f *File) SetCellFloat(sheet, axis string, value float64, prec, bitSize int) error { - rwMutex.Lock() - defer rwMutex.Unlock() xlsx, err := f.workSheetReader(sheet) if err != nil { return err @@ -262,8 +253,6 @@ func setCellFloat(value float64, prec, bitSize int) (t string, v string) { // SetCellStr provides a function to set string type value of a cell. Total // number of characters that a cell can contain 32767 characters. func (f *File) SetCellStr(sheet, axis, value string) error { - rwMutex.Lock() - defer rwMutex.Unlock() xlsx, err := f.workSheetReader(sheet) if err != nil { return err @@ -291,6 +280,8 @@ func (f *File) setCellString(value string) (t string, v string) { // setSharedString provides a function to add string to the share string table. func (f *File) setSharedString(val string) int { sst := f.sharedStringsReader() + f.Lock() + defer f.Unlock() if i, ok := f.sharedStringsMap[val]; ok { return i } @@ -371,8 +362,6 @@ type FormulaOpts struct { // SetCellFormula provides a function to set cell formula by given string and // worksheet name. func (f *File) SetCellFormula(sheet, axis, formula string, opts ...FormulaOpts) error { - rwMutex.Lock() - defer rwMutex.Unlock() xlsx, err := f.workSheetReader(sheet) if err != nil { return err @@ -697,6 +686,8 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error { // getCellInfo does common preparation for all SetCell* methods. func (f *File) prepareCell(xlsx *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) { + xlsx.Lock() + defer xlsx.Unlock() var err error cell, err = f.mergeCellsParser(xlsx, cell) if err != nil { @@ -728,6 +719,9 @@ func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c return "", err } + xlsx.Lock() + defer xlsx.Unlock() + lastRowNum := 0 if l := len(xlsx.SheetData.Row); l > 0 { lastRowNum = xlsx.SheetData.Row[l-1].R diff --git a/cell_test.go b/cell_test.go index fb30596f..ba4cd83b 100644 --- a/cell_test.go +++ b/cell_test.go @@ -4,12 +4,33 @@ import ( "fmt" "path/filepath" "strconv" + "sync" "testing" "time" "github.com/stretchr/testify/assert" ) +func TestConcurrency(t *testing.T) { + f := NewFile() + wg := new(sync.WaitGroup) + for i := 1; i <= 5; i++ { + wg.Add(1) + go func(val int) { + f.SetCellValue("Sheet1", fmt.Sprintf("A%d", val), val) + f.SetCellValue("Sheet1", fmt.Sprintf("B%d", val), strconv.Itoa(val)) + f.GetCellValue("Sheet1", fmt.Sprintf("A%d", val)) + wg.Done() + }(i) + } + wg.Wait() + val, err := f.GetCellValue("Sheet1", "A1") + if err != nil { + t.Error(err) + } + assert.Equal(t, "1", val) +} + func TestCheckCellInArea(t *testing.T) { f := NewFile() expectedTrueCellInAreaList := [][2]string{ diff --git a/excelize.go b/excelize.go index 5cc88e96..bfb3abaa 100644 --- a/excelize.go +++ b/excelize.go @@ -24,12 +24,14 @@ import ( "path" "strconv" "strings" + "sync" "golang.org/x/net/html/charset" ) // File define a populated spreadsheet file struct. type File struct { + sync.RWMutex xmlAttr map[string][]xml.Attr checked map[string]bool sheetMap map[string]string @@ -153,6 +155,8 @@ func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error { // workSheetReader provides a function to get the pointer to the structure // after deserialization by given worksheet name. func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) { + f.Lock() + defer f.Unlock() var ( name string ok bool @@ -323,7 +327,7 @@ func (f *File) AddVBAProject(bin string) error { var err error // Check vbaProject.bin exists first. if _, err = os.Stat(bin); os.IsNotExist(err) { - return err + return fmt.Errorf("stat %s: no such file or directory", bin) } if path.Ext(bin) != ".bin" { return errors.New("unsupported VBA project extension") diff --git a/lib.go b/lib.go index acb45901..88aa3a11 100644 --- a/lib.go +++ b/lib.go @@ -167,7 +167,7 @@ func ColumnNumberToName(num int) (string, error) { } var col string for num > 0 { - col = string((num-1)%26+65) + col + col = string(rune((num-1)%26+65)) + col num = (num - 1) / 26 } return col, nil diff --git a/rows.go b/rows.go index 320ba2fd..66dd16be 100644 --- a/rows.go +++ b/rows.go @@ -284,6 +284,8 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) { func (f *File) sharedStringsReader() *xlsxSST { var err error + f.Lock() + defer f.Unlock() if f.SharedStrings == nil { var sharedStrings xlsxSST ss := f.readXML("xl/sharedStrings.xml") @@ -318,6 +320,8 @@ func (f *File) sharedStringsReader() *xlsxSST { // inteded to be used with for range on rows an argument with the xlsx opened // file. func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) { + f.Lock() + defer f.Unlock() switch xlsx.T { case "s": if xlsx.V != "" { diff --git a/stream_test.go b/stream_test.go index d89dad84..d81b1d4b 100644 --- a/stream_test.go +++ b/stream_test.go @@ -91,6 +91,7 @@ func TestStreamWriter(t *testing.T) { assert.NoError(t, err) _, err = streamWriter.rawData.Reader() assert.NoError(t, err) + assert.NoError(t, streamWriter.rawData.tmp.Close()) assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name())) // Test unsupport charset diff --git a/xmlWorksheet.go b/xmlWorksheet.go index 7cd73c41..2b39e64d 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -11,11 +11,15 @@ package excelize -import "encoding/xml" +import ( + "encoding/xml" + "sync" +) // xlsxWorksheet directly maps the worksheet element in the namespace // http://schemas.openxmlformats.org/spreadsheetml/2006/main. type xlsxWorksheet struct { + sync.RWMutex XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"` SheetPr *xlsxSheetPr `xml:"sheetPr"` Dimension *xlsxDimension `xml:"dimension"`